How can I check error code inside foreach loop? [duplicate] - powershell

This question already has answers here:
How to handle errors for the commands to run in Start-Job?
(1 answer)
Silent installation by using Powershell scripting
(2 answers)
Closed 4 years ago.
I need to make a simple script for DB logical dump. The goal to use the script for two purposes.
If I run it with parameter (DB names) it shall create a logical dump of those DBs.
If I run without parameters it starts command for a list of DBs (hardcoded).
I want to check error code for each command start (pg_dump) inside foreach loop, log it and continue.
What's the best way to do it?
So far I dicovered that I can use try and catch.
Side-note: In my code I tried try..catch only once for testing purposes.
$path = '"C:\Program Files\PostgreSQL\version\bin\pg_dump.exe"'
$backup_path = 'D:\Backups\test\'
$limit = (Get-Date).AddDays(-2)
$logdate = (Get-Date).ToString("yyyy-MM-dd")
$pg_suffix = "pg_dump"
#$LogFile = $backup_path\$pg_suffix_$(Get-Date -f yyyy-MM-dd) + '.log'
$pg_dump_error = "pg_dump has failed"
$p = $args
[array]$DB_Array = #('postgres', 'db02', 'db03')
if ($p -ne $null) {
try {
foreach ($DB in $p) {
$backup_path_temp = $backup_path + $DB + '_' + $(Get-Date -f yyyy-MM-dd) + '.backup'
cmd /c "$path -w -h localhost -U postgres -Z3 -Fd -j 12 -f $backup_path_temp $DB"
}
} catch {
#"Error! $pg_dump_error" | Tee-Object -FilePath $LogFile -Append | Write-Error }
Write "Error: $file.name: $_" >>D:\\Backups\logfile.txt
}
continue;
} else {
foreach ($DB in $DB_Array) {
$backup_path_temp = $backup_path + $DB + '_' + $(Get-Date -f yyyy-MM-dd) + '.backup'
cmd /c "$path -w -h localhost -U postgres -Z3 -Fd -j 12 -f $backup_path_temp $DB"
}
}
# Delete files older than the $limit.
Get-ChildItem -Path $backup_path -Force |
Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } |
Remove-Item -Force -Recurse

Use $LastExitcode automatic variable for getting the exit code of a executable in PowerShell.

Related

PowerShell removes variables from concatenation

I want to iterate over folders and execute a command per folder, ignoring the already processed ones.
For this I'm trying to concatenate variables to produce a command and then execute this string.
But PowerShell (v7.2.5) is removing the variables from concatenation (or replacing them with empty strings).
I tried many different syntaxes like $($var1) + $($var2) or "$var1 $var2", none of them work. My current approach (it will run with Invoke-Expression -Command instead of echo):
$arguments = "-a -b -c"
$exe = "C:\foo\bar.exe"
$targetdir = "C:\path\"
Get-ChildItem $targetdir -Directory | ForEach-Object -Parallel {If($_.FullName.Contains("processed")){continue}; echo ("{0} -d {1} {2}" -f $($exe),$($_.FullName),$($arguments));} -ThrottleLimit 8
Expected:
C:\foo\bar.exe -d C:\path\101 -a -b -c
C:\foo\bar.exe -d C:\path\102 -a -b -c
C:\foo\bar.exe -d C:\path\103 -a -b -c
Output:
-d C:\path\101
-d C:\path\102
-d C:\path\103
Why is PowerShell removing paths or arguments from the concatenation and how do I fix this?
As stated in the the ForEach-Object MS Docs:
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread.
Do not use continue outside of a loop, switch, or trap:
Using continue inside a pipeline, such as a ForEach-Object script block, not only exits the pipeline, it potentially terminates the entire runspace.
You can use return to emulate the behavior that continue has on a loop:
$arguments = '-a -b -c'
$exe = 'C:\foo\bar.exe'
$targetdir = 'C:\path'
Get-ChildItem $targetdir -Directory | ForEach-Object -Parallel {
if($_.FullName.Contains('processed')) {
return
}
"{0} -d {1} {2}" -f $using:exe, $_.FullName, $using:arguments
} -ThrottleLimit 8

How to Get Start-Job working with TimeStamp Pipe [duplicate]

This question already has answers here:
How to pass a custom function inside a ForEach-Object -Parallel
(3 answers)
PowerShell Use UDF in ForEach-Object -Parallel [duplicate]
(1 answer)
Closed 10 months ago.
I have a powershell line that works great:
Start-Job { plink.exe -telnet 000.000.000.000 -P 1000 > LogFile.txt }
Job runs as expected in the background, text file is created.
When I throw TimeStamp in there than run the job, Get-Job shows it completed, and the file is not created. Not sure if it makes the connection or not in this case.
Start-Job { plink.exe -telnet 000.000.000.000 -P 1000 | TimeStamp > LogFile.txt }
When I run the above without Start-Job and with TimeStamp it works fine. Obviously it is finishing the job at TimeStamp. Any ideas for a workaround or another way to get the timestamp in the log file. Running it as a job is the way i want to go mainly So I can stop it at a specific time, parse the logs than restart with a new file name. Here the rest of the script:
filter timestamp {"$(Get-Date -Format MM/dd/yyyy_HH:mm:ss) $_"}
Start-Job -Name Logging { c:\plink.exe -telnet 000.000.000.000 -P 10000 | TimeStamp
> c:\users\User\"LogFile_$(get-date -f MM-dd-yyyy).txt" }
[datetime]$JobEndTime = '23:50'
$AddDaysWhenInPast = 1
[datetime]$CurrentTime = Get-Date
If ($JobEndTime -lt $CurrentTime) { $JobEndTime =
$JobEndTime.AddDays($AddDaysWhenInPast) }
[int]$WaitSeconds = ( $JobEndTime - $CurrentTime ).TotalSeconds
#Debug
#write-Host $JobEndTime
#Write-Host $AddDaysWhenInPast
#Write-Host $CurrentTime
#Write-Host $WaitSeconds
#Write-Host $AddDaysWhenInPast
#Pause
Stop-Job logging
Set-Content -Path C:\users\User\"LogFile_$(get-date -f MM-dd-yyyy).txt" -Value (get-
content -Path C:\users\users\User\"LogFile_$(get-date -f MM-dd-yyyy).txt" | Where-
Object { $i % 2 -eq 0; $i++ })
Set-Content -Path C:\users\User\"LogFile_$(get-date -f MM-dd-yyyy).txt" -Value (get-
content -Path C:\users\User\"LogFile_$(get-date -f MM-dd-yyyy).txt" | Select-String -
Pattern '00000' -NotMatch)
(loops back to top)

Start-Process with Process Priority in Loop doesn't recognize variable

I'm using FFmpeg with PowerShell.
I have a loop that goes through a folder of mpg files and grabs the names to a variable $inputName.
FFmpeg then converts each one to an mp4.
Works
Batch Processing
$files = Get-ChildItem "C:\Path\" -Filter *.mpg;
foreach ($f in $files) {
$inputName = $f.Name; #name + extension
$outputName = (Get-Item $inputName).Basename; #name only
ffmpeg -y -i "C:\Users\Matt\Videos\$inputName" -c:v libx264 -crf 25 "C:\Users\Matt\Videos\$outputName.mp4"
}
Not Working
Batch Processing with Process Priority
$files = Get-ChildItem "C:\Path\" -Filter *.mpg;
foreach ($f in $files) {
$inputName = $f.Name; #name + extension
$outputName = (Get-Item $inputName).Basename; #name only
($Process = Start-Process ffmpeg -NoNewWindow -ArgumentList '-y -i "C:\Users\Matt\Videos\$inputName" -c:v libx264 -crf 25 "C:\Users\Matt\Videos\$outputName.mp4"' -PassThru).PriorityClass = [System.Diagnostics.ProcessPriorityClass]::AboveNormal;
Wait-Process -Id $Process.id
}
If I set the Process Priority using Start-Process PriorityClass, the $inputName variable is no longer recognized.
Error:
C:\Users\Matt\Videos\$inputName: No such file or directory
Lets go over a few basic things.
In powershell we love piping |, It allows use to pass the information from one command to another command.
A good example of this is the ForEach you have.
Instead of Foreach($F in $Files) you can pipe | into a foreach-object
Get-ChildItem "C:\Path\" -Filter *.mpg | Foreach-Object{
$_
}
When Piping | a command powershell automatically creates the variable $_ which is the object that is passed in the pipe |
The next thing is there are 2 types of quotes " and '.
If you use ' then everthing is taken literally. Example
$FirstName = "TestName"
'Hey There $FirstName'
Will return
Hey There $FirstName
While " allows you to use Variables in it. Example
$FirstName = "TestName"
'Hey There $FirstName'
Will return
Hey There TestName
Now one last thing before we fix this. In powershell we have a escape char ` aka a tick. Its located beside the number 1 on the keyboard with the tilde. You use it to allow the use of char that would otherwise break out of the qoutes. Example
"`"Hey There`""
Would return
"Hey There"
OK so now that we covered the basics lets fix up the script
Get-ChildItem "C:\Users\Matt\Videos\" -Filter *.mpg -File | Foreach-Object{
($Process = Start-Process ffmpeg -NoNewWindow -ArgumentList "-y -i `"$($_.FullName)`" -c:v libx264 -crf 25 `"C:\Users\Matt\Videos\$($_.Name)`"" -PassThru).PriorityClass = [System.Diagnostics.ProcessPriorityClass]::AboveNormal;
Try{
Wait-Process -Id $Process.id
}catch{
}
}
In the case above I changed
Add -File to the Get-ChildItem to designate that you only want Files returned not folders
Pipe | into a Foreach-Object
Changed the Outside Brackets in the -ArgumentList to be double quotes " instead of literal quotes '
Removed the $InputName and $OutputName in favor of the Foreach-Object variable $_

How to format the output error so that server is not listed twice?

I have this script:
$params = '/r', '/f',
'/t', '0',
'/d', 'p:0:0',
'/c', 'PlannedRestart'
$servers | ForEach-Object {
$output = & shutdown.exe /m "\\${_}" #params 2>&1
if ($LastExitCode -eq 0) {
"{0}`tRestarted" -f $_
} else {
"{0}`tRestart failed:`t{1}" -f $_, $output
}
} | Set-Content '.\RestartServers_LOG.txt'
it prints like this when it fails:
server1 Restart failed:server1:error
i want it to print:
server1 Restart failed:error
If the hostname always appears at the beginning of the captured output you can remove it with a simple replacement, e.g. like this:
"{0}`tRestart failed:`t{1}" -f $_, ($output -replace "^${_}:")
If it can appear at different locations in the output string you need to provide a more complete output example.

How to decorate an existing function or passing function objects in Powershell?

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?
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.