Reputation: 4802
Lets say I want to save the output of a Powershell command to file. I would do this like ls | out-file "path.txt"
. I make this call a few times per day and am worried that the function call (ls
in this case) produce bad data ruining my file. I feel like I need a backup!
Next step for me would be decorating the out-file
call so that it automatically backs up the data in a separate file. One backup per day would be sufficient. This could be achieved by a custom out-bak
function as per below. Suddenly I get automated backups with ls | Out-Bak "path.txt"
.
function Out-Bak {
[cmdletbinding()]
Param (
[parameter(ValueFromPipeline)]
[string]$inputObject,
[parameter(Mandatory=$false)]
[string]$myPath
)
Begin {
$backupPath = [System.IO.Path]::ChangeExtension($myPath,".bak_$([DateTime]::Now.ToShortDateString())")
Remove-Item $myPath
Remove-Item $backupPath
}
Process {
Out-File -InputObject $input -FilePath $myPath -Append
Out-File -InputObject $input -FilePath $backupPath -Append
}
}
This solves my problem fine, but I would like to be able to use exactly the same pattern for Out-csv
and similar filewriting fucntion. Is there a way to pass the Out-File
command as a parameter to the Out-Bak
so that I can use the function as a somewhat generic decorator for output commands?
Upvotes: 2
Views: 385
Reputation: 73746
Let the backup function do only what its name suggests: backup the file.
ls | Out-File $path | Backup
........ | Out-File foo.txt | Backup
........ | Out-File -FilePath "$path\$name" | Backup
........ | Export-Csv -NoTypeInformation bar.csv | Backup
The backup
cmdlet will simply copy the file once the pipeline finishes.
To find the file path from a previous pipeline command we'll have to use arcane stuff like AST parser:
function Backup {
end {
$bakCmdText = (Get-PSCallStack)[1].Position.text
$bakCmd = [ScriptBlock]::Create($bakCmdText).
Ast.EndBlock.Statements[0].PipelineElements[-2].CommandElements
$bakParamInfo = if (!$bakCmd) { @{} }
else { @{} + (Get-Command ($bakCmd[0].value)).Parameters }
$bakSource = ''; $bakLiteral = $false; $bakPos = 0
while (!$bakSource -and ++$bakPos -lt $bakCmd.count) {
$bakToken = $bakCmd[$bakPos]
if ($bakToken.ParameterName) {
if ($bakToken.ParameterName -match '^(File|Literal)?Path$') {
$bakLiteral = $bakToken.ParameterName -eq 'LiteralPath'
} elseif (!$bakParamInfo[$bakToken.ParameterName].SwitchParameter) {
$bakPos++
}
continue
}
$bakSource = if ($bakToken.StringConstantType -in 'SingleQuoted', 'BareWord') {
$bakToken.value
} else {
[ScriptBlock]::Create($bakToken.extent.text).
InvokeWithContext(@{}, (Get-Variable bak* -scope 1))
}
}
if (!$bakSource) {
Write-Warning "Could not find file path in pipeline emitter: $bakCmdText"
return
}
$backupTarget = "$bakSource" + '.' + [DateTime]::Now.ToShortDateString() + '.bak'
$bakParams = @{ $(if ($bakLiteral) {'LiteralPath'} else {'Path'}) = "$bakSource" }
copy @bakParams -destination $backupTarget -Force
}
}
Warning: it fails with $()
like ... | out-file "$($path)" | backup
because Get-PSCallStack for some reason returns the expression contents as the callee, and right now I don't know other methods of getting the parent invocation context.
Upvotes: 3