Get batch file return code in powershell - powershell

I'm trying to write a PowerShell script as a "Framework". Basically what it does is calls batch files that installs msi files locally.
There are few things I am trying to get done.
PowerShell should get all exit codes whatever the batch files return.
Need a timer and check if the batch file is stuck.If it's stuck kill it, return an error and continue
Need a progress bar for each batch file installation.
I'm not sure if I can do all or any. Since every package(the batch files) comes from different groups, I can't use PowerShell to do everything and have to use the batch files other groups sent me.
I was able to get the packages install with a simple script but couldn't get to do any of the above things I was trying to do. I keep getting last exit code as 0. I assume it's because the batch file ran successfully.
$APP1 = "Microsoft_RDP_8.1_L_EN_01"
Stop-Process -Name reg* -Force
Write-Host "=== $Time -- Starting $APP1 Installation"
TRY
{
$Install = "$pwd\cmd\$App1\Install.cmd"
cmd /c $Install /b 1
$App1ErrorCode = $LastExitCode
Write-Host "Final Return Code Of $APP1 Is = $APP1ErrorCode"
}
CATCH
{
Write-Host "-----------------------------------------------------------" -ForegroundColor Green
Write-Host "Caught an exception:" -ForegroundColor Red
Write-Host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
Write-Host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "-----------------------------------------------------------" -ForegroundColor Green
}
$LastExitCode = $null
$Value = $null

Related

Catching errors in powershell workflow

I'm relatively new to powershell scripting so I have been coding based on multiple examples that I have seen online.
I have a script that executes multiple batch files in parallel and each batch file contains a bcp command to execute. I'm trying to catch any errors that may occur running the batch file but it's not working as expected. I specifically forced an error on product.bat by having an invalid select syntax.
workflow Test-Workflow
{
Param ([string[]] $file_names)
$file_names = Get-Content "D:/EDW/data/informatica/ming/Powersh/bcplist.lst"
foreach -parallel ($line in $file_names)
{
try
{
Write-Output ("processing... " + $line + ".bat")
start-process D:/EDW/data/informatica/ming/Powersh/$line.bat -ErrorAction Stop -wait
}
catch
{
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
Write-Output $line : $ErrorMessage $FailedItem
}
}
}
bcplist.lst:
ing_channel
ing_product
ing_channel:
bcp "SELECT * FROM CHANNEL" queryout ing_channel.txt -T -S99.999.999.9,99999 -t"\t" -c -q
ing_product:
bcp "SELT * FROM PRODUCT" queryout ing_product.txt -T -S99.999.999.9,99999 -t"\t" -c -q
Any help or suggestion would be greatly appreciated.
Exceptions are only thrown/caught when terminating errors are thrown, which are only thrown by cmdlets, .NET libraries, or native code when P/Invoke is in play. In order to handle failures with external commands, such as checking whether a bat or exe succeeded, you will need to check the $LASTEXITCODE yourself. $LASTEXITCODE is the PowerShell equivalent of %ERRORLEVEL% in cmd.exe. Here is an example of some basic boilerplate code to check this for the ping command:
&ping nonexistant.domain.tld
if( $LASTEXITCODE -ne 0 ){
# Handle the error here
# This example writes to the error stream and throws a terminating error
Write-Error "Unable to ping server, ping returned $LASTEXITCODE" -EA Stop
}
Note that the -ErrorAction argument has a shorthand of -EA, so either the long or short form will work.

Continue script, once search will be done

I have command in the script to do compliance search(Microsoft Exchange) and it takes ~20 minutes and I need to wait until it will be done to use powershell. I need to continue script automatically, once search will be done. Here is example:
ShowSearchResults
Write-Host "You can find search results under the following path: C:\ExportResults" -ForegroundColor Green
Write-Host "Do you want to delete found mails? (y/n)" -ForegroundColor Green
New-ComplianceSearchAction -SearchName $FinalSearchName -PurgeType softdelete -Purge
And when I start "ShowSearchResults" I need to wait once it will be done. I need to show "Write-Host..." once search will be done, until it, I need my session.
I tried with Start-Job, but I didn't find the solution with this way
Your code seems good to me except for 1 misrepresented statement.
PowerShell, like most scripting languages, will execute the commands/statements in your script from top to bottom by default.
So your Write-Host will be executed only after the previous command is completed.
As for the "misrepresented" statement, you might've wanted to use Read-Host to get a user choice to soft delete your last search. So after small corrections, your code looks like the below,
#1
$ComplianceSearchJob = Start-Job -ScriptBlock {
ShowSearchResults
Write-Host "You can find search results under the following path: C:\ExportResults" -ForegroundColor Green
# Invoke-Item "C:\ExportResults\SearchResults.csv"
}
#2
Receive-Job $ComplianceSearchJob -Keep
#3
$DeleteChoice = Read-Host "Do you want to delete found mails? (y/n): " -ForegroundColor Green
if($DeleteChoice -eq "Y"){ # case insensitive
New-ComplianceSearchAction -SearchName $FinalSearchName -PurgeType softdelete -Purge
}
EDIT 1: Added Invoke-Item to open the C:\ExportResults\SearchResults.csv file in a comment.
EDIT 2: Used Start-Job to allow running as a background process.

Prorgess bar for `New-MailboxExportRequest`

I'm trying to create a script that can export a user's mailbox to a PST, remotely (Exchange Server 2010 console is installed on the server we're running this from, and the module is loaded correctly). It's being done using a script so our L2 admins do not have to manually perform the task. Here's the MWE.
$UserID = Read-Host "Enter username"
$PstDestination = "\\ExServer\Share\$UserID.pst"
$Date = Get-Date -Format "yyyyMMddhhmmss"
$ExportName = "$UserID" + "$Date"
try {
New-MailboxExportRequest -Mailbox $UserID -FilePath $PstDestination -Name $ExportName -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null
# Loop through the process to track its status and write progress
do {
$Percentage = (Get-MailboxExportRequest -Name $ExportName | Get-MailboxExportRequestStatistics).PercentComplete
Write-Progress "Mailbox export is in progress." -Status "Export $Percentage% complete" -PercentComplete "$Percentage"
}
while ($Percentage -ne 100)
Write-Output "$UserID`'s mailbox has been successfully exported. The archive can be found at $PstDestination."
}
catch {
Write-Output "There was an error exporting the mailbox. The process was aborted."
}
The problem is, as soon as we initiate the export, the task gets Queued. Sometimes, the export remains queued for a very long time, and the script is currently unable to figure out when the task begins, and when it does, is unable to display the progress correctly. The export happens in the background, but the script remains stuck there. So anything after the export, does not get executed, and the whole thing then has to be done manually.
Please suggest a way to handle this?
I tried adding a wait timer and then a check to see if the export has begun. It didn't quite work as expected.
Two things. First one is more about performance/hammering Exchange with unnesacary requests in do/while loop. Start-Sleep -Seconds 1 (or any other delay that makes sense depending on the mailbox size(s)) inside the loop is a must.
Second: rather than wait for job to start, just resume it yourself:
if ($request.Status -eq 'Queued') {
$request | Resume-MailboxExportRequest
}

Powershell: Brute-forcing password-protected .zip file (speeding up the process)

First-time questioner, so here's hoping I'm doing it right. :)
A co-worker and I have been playing around with Powershell, getting the lay of the land and see what you can do with it. Using info we found online (mostly here), we've managed to whip together a script to brute-force a password-protected .zip file using a .txt containing a list of passwords:
# Stopwatch for measurement
$stopWatch = [System.Diagnostics.Stopwatch]::startNew()
$7zipExec = """-7z.exe (7zip) location-"""
$input = """-.zip location-"""
$output = """-where to drop contents of .zip file-"""
$passwordfile = "-location of .txt file containing passwords-"
$windowStyle = "Hidden"
[long] $counter = 0
# Correct password is 12341234
foreach ($password in (get-content $passwordfile)) {
$counter++
Write-Host -NoNewLine "Attempt #($counter): $password"
$arguments = "x -o$output -p$password -aoa $input"
$p = Start-Process $7zipExec -ArgumentList $arguments -Wait -PassThru -WindowStyle $windowStyle
if ($p.ExitCode -eq 0) {
# Password OK
Write-Host " ...OK!"
Write-Host ""
Write-Host "Password is $password, found it after $counter tries."
break
}
elseif ($p.ExitCode -eq 2) {
# Wrong password
Write-Host " ...wrong"
}
else {
# Unknown
Write-Host " ...ERROR"
}
}
# Halt the stopwatch and display the time spent for this process
$stopWatch.Stop()
Write-Host
Write-Host "Done in $($stopWatch.Elapsed.Hours) hour(s), $($stopWatch.Elapsed.Minutes) minute(s) and $($stopWatch.Elapsed.Seconds) seconds(s)"
Read-Host -Prompt "Press Enter to exit"
It actually works! Probably not as clean as it could be, but we've managed to reach our goal to make a functioning script.
However! It takes about 1 second for each password try, and if you have a file with, say, the 10,000 most common passwords...that could take a while.
So now we're trying to figure out how to speed up the process, but we've hit a wall and need help. I'm not asking for someone to get 'er done, but I would really appreciate some tips/tricks/hints for someone who has only recently started getting into Powershell (and loving it so far!).
Took a while to get back to this, real life and all that, but while I did not manage to speed up the script, I did manage to speed up the process.
What I do now is run 4 instances of the script simultaneously (using an extra PS script to start them, which itself can be started with a batch file).
All of them have their own lists of passwords, and their own output directory (I found that when they use the same location, the file extracted by the script that found the password becomes unusable).
This way, it takes about 7-8 hours to attempt 100,000 of the most commonly used passwords! While I'm sure there are quicker scripts/programs out there, I'm pretty happy with the result.
Thanks all for the input!

Execute code if a PowerShell script is terminated

Is it possible to force the execution of some code if a PowerShell script is forcefully terminated? I have tried try..finally and Traps, but they both don't seem to work, at least when I press Ctrl-C from PowerShell ISE.
Basically, I have a Jenkins build that executes a PowerShell script. If for any reason I want to stop the build from within Jenkins, I don't want any subprocess to lock the files, hence keeping my build project in a broken state until an admin manually kill the offending processes (nunit-agent.exe in my case). So I want to be able to force the execution of a code that terminates nunit-agent.exe if this happens.
UPDATE: As #Frode suggested below, I tried to use try..finally:
$sleep = {
try {
Write-Output "In the try block of the job."
Start-Sleep -Seconds 10
}
finally {
Write-Output "In the finally block of the job."
}
}
try {
$sleepJob = Start-Job -ScriptBlock $sleep
Start-Sleep -Seconds 5
}
finally {
Write-Output "In the finaly block of the script."
Stop-Job $sleepJob
Write-Output "Receiving the output from the job:"
$content = Receive-Job $sleepJob
Write-Output $content
}
Then when I executed this and broke the process using Ctrl-C, I got no output. I thought that what I should got is:
In the finally block of the script.
Receiving the output from the job:
In the try block of the job.
In the finally block of the job.
I use try {} finally {} for this. The finally-block runs when try is done or if you use ctrl+c, so you need to either run commands that are safe to run either way, ex. it doesn't matter if you kill a process that's already dead..
Or you could add a test to see if the last command was a success using $?, ex:
try {
Write-Host "Working"
Start-Sleep -Seconds 100
} finally {
if(-not $?) { Write-Host "Cleanup on aisle 5" }
Write-Host "Done"
}
Or create your own test (just in case the last command in try failed for some reason):
try {
$IsDone = $false
Write-Host "Working"
Start-Sleep -Seconds 100
#.....
$IsDone = $true
} finally {
if(-not $IsDone) { Write-Host "Cleanup on aisle 5" }
Write-Host "Done"
}
UPDATE: The finally block will not work for output as the pipeline is stopped on CTRL+C.
Note that pressing CTRL+C stops the pipeline. Objects that are sent to
the pipeline will not be displayed as output. Therefore, if you
include a statement to be displayed, such as "Finally block has run",
it will not be displayed after you press CTRL+C, even if the Finally
block ran.
Source: about_Try_Catch_Finally
However, if you save the output from Receive-Job to a global variable like $global:content = Receive-Job $sleepJob you can read it after the finally-block. The variable is normally created in a different local scope and lost after the finally-block.