diff options
author | Brian Lloyd <brian.d.lloyd@gmail.com> | 2015-07-29 19:38:15 -0400 |
---|---|---|
committer | Brian Lloyd <brian.d.lloyd@gmail.com> | 2015-07-29 19:38:15 -0400 |
commit | fc2a33b96613dacd2d00860126f6fa4ffb3c4e50 (patch) | |
tree | f62bcc424d03efbb604ca79c22616e1b66559f11 /windows/win_lineinfile.ps1 | |
parent | 2785364ebec62c5046e20c32384726d3ef6d5404 (diff) | |
download | ansible-modules-core-fc2a33b96613dacd2d00860126f6fa4ffb3c4e50.tar.gz |
Windows implementation of lineinfile and related documentation
Diffstat (limited to 'windows/win_lineinfile.ps1')
-rw-r--r-- | windows/win_lineinfile.ps1 | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/windows/win_lineinfile.ps1 b/windows/win_lineinfile.ps1 new file mode 100644 index 00000000..ddf1d4e3 --- /dev/null +++ b/windows/win_lineinfile.ps1 @@ -0,0 +1,452 @@ +#!powershell +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# WANT_JSON +# POWERSHELL_COMMON + + +# Parse the parameters file dropped by the Ansible machinery + +$params = Parse-Args $args; + + +# Initialize defaults for input parameters. + +$dest= Get-Attr $params "dest" $FALSE; +$regexp = Get-Attr $params "regexp" $FALSE; +$state = Get-Attr $params "state" "present"; +$line = Get-Attr $params "line" $FALSE; +$backrefs = Get-Attr $params "backrefs" "no"; +$insertafter = Get-Attr $params "insertafter" $FALSE; +$insertbefore = Get-Attr $params "insertbefore" $FALSE; +$create = Get-Attr $params "create" "no"; +$backup = Get-Attr $params "backup" "no"; +$validate = Get-Attr $params "validate" $FALSE; +$encoding = Get-Attr $params "encoding" "auto"; +$newline = Get-Attr $params "newline" "windows"; + + +# Parse dest / name /destfile param aliases for compatibility with lineinfile +# and fail if at least one spelling of the parameter is not provided. + +$dest = Get-Attr $params "dest" $FALSE; +If ($dest -eq $FALSE) { + $dest = Get-Attr $params "name" $FALSE; + If ($dest -eq $FALSE) { + $dest = Get-Attr $params "destfile" $FALSE; + If ($dest -eq $FALSE) { + Fail-Json (New-Object psobject) "missing required argument: dest"; + } + } +} + + +# Fail if the destination is not a file + +If (Test-Path $dest -pathType container) { + Fail-Json (New-Object psobject) "destination is a directory"; +} + + +# Write lines to a file using the specified line separator and encoding, +# performing validation if a validation command was specified. + +function WriteLines($outlines, $dest, $linesep, $encodingobj, $validate) { + $temppath = [System.IO.Path]::GetTempFileName(); + $joined = $outlines -join $linesep; + [System.IO.File]::WriteAllText($temppath, $joined, $encodingobj); + + If ($validate -ne $FALSE) { + + If (!($validate -like "*%s*")) { + Fail-Json (New-Object psobject) "validate must contain %s: $validate"; + } + + $validate = $validate.Replace("%s", $temppath); + + $parts = [System.Collections.ArrayList] $validate.Split(" "); + $cmdname = $parts[0]; + + $cmdargs = $validate.Substring($cmdname.Length + 1); + + $process = [Diagnostics.Process]::Start($cmdname, $cmdargs); + $process.WaitForExit(); + + If ($process.ExitCode -ne 0) { + [string] $output = $process.StandardOutput.ReadToEnd(); + [string] $error = $process.StandardError.ReadToEnd(); + Remove-Item $temppath -force; + Fail-Json (New-Object psobject) "failed to validate $cmdname $cmdargs with error: $output $error"; + } + + } + + # Commit changes to the destination file + $cleandest = $dest.Replace("/", "\"); + Move-Item $temppath $cleandest -force; +} + + +# Backup the file specified with a date/time filename + +function BackupFile($path) { + $backuppath = $path + "." + [DateTime]::Now.ToString("yyyyMMdd-HHmmss"); + Copy-Item $path $backuppath; + return $backuppath; +} + + + +# Implement the functionality for state == 'present' + +function Present($dest, $regexp, $line, $insertafter, $insertbefore, $create, $backup, $backrefs, $validate, $encodingobj, $linesep) { + + # Note that we have to clean up the dest path because ansible wants to treat / and \ as + # interchangable in windows pathnames, but .NET framework internals do not support that. + $cleandest = $dest.Replace("/", "\"); + + # Check if destination exists. If it does not exist, either create it if create == "yes" + # was specified or fail with a reasonable error message. + If (!(Test-Path $dest)) { + If ($create -eq "no") { + Fail-Json (New-Object psobject) "Destination $dest does not exist !"; + } + # Create new empty file, using the specified encoding to write correct BOM + [System.IO.File]::WriteAllLines($cleandest, "", $encodingobj); + } + + # Read the dest file lines using the indicated encoding into a mutable ArrayList. + $content = [System.IO.File]::ReadAllLines($cleandest, $encodingobj); + If ($content -eq $null) { + $lines = New-Object System.Collections.ArrayList; + } + Else { + $lines = [System.Collections.ArrayList] $content; + } + + # Compile the regex specified, if provided + $mre = $FALSE; + If ($regexp -ne $FALSE) { + $mre = New-Object Regex $regexp, 'Compiled'; + } + + # Compile the regex for insertafter or insertbefore, if provided + $insre = $FALSE; + + If ($insertafter -ne $FALSE -and $insertafter -ne "BOF" -and $insertafter -ne "EOF") { + $insre = New-Object Regex $insertafter, 'Compiled'; + } + ElseIf ($insertbefore -ne $FALSE -and $insertbefore -ne "BOF") { + $insre = New-Object Regex $insertbefore, 'Compiled'; + } + + # index[0] is the line num where regexp has been found + # index[1] is the line num where insertafter/inserbefore has been found + $index = -1, -1; + $lineno = 0; + + # The latest match object and matched line + $matched_line = ""; + $m = $FALSE; + + # Iterate through the lines in the file looking for matches + Foreach ($cur_line in $lines) { + If ($regexp -ne $FALSE) { + $m = $mre.Match($cur_line); + $match_found = $m.Success; + If ($match_found) { + $matched_line = $cur_line; + } + } + Else { + $match_found = $line -ceq $cur_line; + } + If ($match_found) { + $index[0] = $lineno; + } + ElseIf ($insre -ne $FALSE -and $insre.Match($cur_line).Success) { + If ($insertafter -ne $FALSE) { + $index[1] = $lineno + 1; + } + If ($insertbefore -ne $FALSE) { + $index[1] = $lineno; + } + } + $lineno = $lineno + 1; + } + + $changed = $FALSE; + $msg = ""; + + If ($index[0] -ne -1) { + If ($backrefs -ne "no") { + $new_line = [regex]::Replace($matched_line, $regexp, $line); + } + Else { + $new_line = $line; + } + If ($lines[$index[0]] -cne $new_line) { + $lines[$index[0]] = $new_line; + $msg = "line replaced"; + $changed = $TRUE; + } + } + ElseIf ($backrefs -ne "no") { + # No matches - no-op + } + ElseIf ($insertbefore -eq "BOF" -or $insertafter -eq "BOF") { + $lines.Insert(0, $line); + $msg = "line added"; + $changed = $TRUE; + } + ElseIf ($insertafter -eq "EOF" -or $index[1] -eq -1) { + $lines.Add($line); + $msg = "line added"; + $changed = $TRUE; + } + Else { + $lines.Insert($index[1], $line); + $msg = "line added"; + $changed = $TRUE; + } + + # Write backup file if backup == "yes" + $backupdest = ""; + + If ($changed -eq $TRUE -and $backup -eq "yes") { + $backupdest = BackupFile $dest; + } + + # Write changes to the destination file if changes were made + If ($changed) { + WriteLines $lines $dest $linesep $encodingobj $validate; + } + + $encodingstr = $encodingobj.WebName; + + # Return result information + $result = New-Object psobject @{ + changed = $changed + msg = $msg + backup = $backupdest + encoding = $encodingstr + } + + Exit-Json $result; +} + + +# Implement the functionality for state == 'absent' + +function Absent($dest, $regexp, $line, $backup, $validate, $encodingobj, $linesep) { + + # Check if destination exists. If it does not exist, fail with a reasonable error message. + If (!(Test-Path $dest)) { + Fail-Json (New-Object psobject) "Destination $dest does not exist !"; + } + + # Read the dest file lines using the indicated encoding into a mutable ArrayList. Note + # that we have to clean up the dest path because ansible wants to treat / and \ as + # interchangeable in windows pathnames, but .NET framework internals do not support that. + + $cleandest = $dest.Replace("/", "\"); + $content = [System.IO.File]::ReadAllLines($cleandest, $encodingobj); + If ($content -eq $null) { + $lines = New-Object System.Collections.ArrayList; + } + Else { + $lines = [System.Collections.ArrayList] $content; + } + + # Initialize message to be returned on success + $msg = ""; + + # Compile the regex specified, if provided + $cre = $FALSE; + If ($regexp -ne $FALSE) { + $cre = New-Object Regex $regexp, 'Compiled'; + } + + $found = New-Object System.Collections.ArrayList; + $left = New-Object System.Collections.ArrayList; + $changed = $FALSE; + + Foreach ($cur_line in $lines) { + If ($cre -ne $FALSE) { + $m = $cre.Match($cur_line); + $match_found = $m.Success; + } + Else { + $match_found = $line -ceq $cur_line; + } + If ($match_found) { + $found.Add($cur_line); + $changed = $TRUE; + } + Else { + $left.Add($cur_line); + } + } + + # Write backup file if backup == "yes" + $backupdest = ""; + + If ($changed -eq $TRUE -and $backup -eq "yes") { + $backupdest = BackupFile $dest; + } + + # Write changes to the destination file if changes were made + If ($changed) { + WriteLines $left $dest $linesep $encodingobj $validate; + } + + # Return result information + $fcount = $found.Count; + $msg = "$fcount line(s) removed"; + $encodingstr = $encodingobj.WebName; + + $result = New-Object psobject @{ + changed = $changed + msg = $msg + backup = $backupdest + found = $fcount + encoding = $encodingstr + } + + Exit-Json $result; +} + + +# Default to windows line separator - probably most common + +$linesep = "`r`n"; + +If ($newline -ne "windows") { + $linesep = "`n"; +} + + +# Fix any CR/LF literals in the line argument. PS will not recognize either backslash +# or backtick literals in the incoming string argument without this bit of black magic. + +If ($line -ne $FALSE) { + $line = $line.Replace("\r", "`r"); + $line = $line.Replace("\n", "`n"); +} + + +# Figure out the proper encoding to use for reading / writing the target file. + +# The default encoding is UTF-8 without BOM +$encodingobj = [System.Text.UTF8Encoding] $FALSE; + +# If an explicit encoding is specified, use that instead +If ($encoding -ne "auto") { + $encodingobj = [System.Text.Encoding]::GetEncoding($encoding); +} + +# Otherwise see if we can determine the current encoding of the target file. +# If the file doesn't exist yet (create == 'yes') we use the default or +# explicitly specified encoding set above. +Elseif (Test-Path $dest) { + + # Get a sorted list of encodings with preambles, longest first + + $max_preamble_len = 0; + $sortedlist = New-Object System.Collections.SortedList; + Foreach ($encodinginfo in [System.Text.Encoding]::GetEncodings()) { + $encoding = $encodinginfo.GetEncoding(); + $plen = $encoding.GetPreamble().Length; + If ($plen -gt $max_preamble_len) { + $max_preamble_len = $plen; + } + If ($plen -gt 0) { + $sortedlist.Add(-($plen * 1000000 + $encoding.CodePage), $encoding); + } + } + + # Get the first N bytes from the file, where N is the max preamble length we saw + + [Byte[]]$bom = Get-Content -Encoding Byte -ReadCount $max_preamble_len -TotalCount $max_preamble_len -Path $dest; + + # Iterate through the sorted encodings, looking for a full match. + + $found = $FALSE; + Foreach ($encoding in $sortedlist.GetValueList()) { + $preamble = $encoding.GetPreamble(); + If ($preamble) { + Foreach ($i in 0..$preamble.Length) { + If ($preamble[$i] -ne $bom[$i]) { + break; + } + Elseif ($i + 1 -eq $preamble.Length) { + $encodingobj = $encoding; + $found = $TRUE; + } + } + If ($found) { + break; + } + } + } +} + + +# Main dispatch - based on the value of 'state', perform argument validation and +# call the appropriate handler function. + +If ($state -eq "present") { + + If ( $backrefs -ne "no" -and $regexp -eq $FALSE ) { + Fail-Json (New-Object psobject) "regexp= is required with backrefs=true"; + } + + If ($line -eq $FALSE) { + Fail-Json (New-Object psobject) "line= is required with state=present"; + } + + If ($insertbefore -eq $FALSE -and $insertafter -eq $FALSE) { + $insertafter = "EOF"; + } + + Present $dest $regexp $line $insertafter $insertbefore $create $backup $backrefs $validate $encodingobj $linesep; + +} +Else { + + If ($regex -eq $FALSE -and $line -eq $FALSE) { + Fail-Json (New-Object psobject) "one of line= or regexp= is required with state=absent"; + } + + Absent $dest $regexp $line $backup $validate $encodingobj $linesep; +} + + + + + + + + + + + + + + + + + |