Event for when PowerShell finishes a command? - powershell

I am looking to create a InputObject for The Register-ObjectEvent cmdlet.
The object needs to hold the event for when a PowerShell command finishes.
So if I type dir into PowerShell it gets registered with the Register-ObjectEvent.
I am having trouble finding the event for when powershell finishes a command, or if there is even one?
My code at the moment is just:
PS C:\>$CommandFinishWatcher = /* Watches for a finished powershell command. */
PS C:\>register-objectEvent -InputObject $CommandFinishWatcher -EventName "PowerShellCommandFinished"

You can use the automatic variable $? to check the status of the most recent command that powershell ran. It contains the execution status of the last operation.
https://ss64.com/ps/syntax-automatic-variables.html
Get-Content -path C:\Test
if($? = "FALSE")
{Write-Host "The get-content command failed."}
if($? = "TRUE")
{Write-Host "The get-content command succeeded."}

Related

Exit PowerShell script in controlled fashion using keyboard shortcut

I have a PowerShell script that performs numerous file management tasks and occasionally I have the need to terminate the script before it has finished processing. In order to terminate the script cleanly and not leave any files dotted around, I have the script read in a variable from a config file every time the foreach of the gci results is processed, as below:
Get-ChildItem $fileDir -Filter *.doc | Foreach-Object {
Get-Content $confFile | Foreach-Object {
$var = $_.Split('=')
New-Variable -Name $var[0] -Value $var[1] -Force
}
if ($process -eq "TRUE") {
<process File operations>
}
}
This allows me to change the value of process in the config file to anything but TRUE and the script will skip the process (although still loop though until complete).
Is there anyway to use a keyboard shortcut to exit the script in a controlled fashion? i.e. after an iteration of the foreach loop has completed. e.g. Press Ctrl+Q and it will exit cleanly as opposed to Ctrl+C.

Wait for an Active Directory query to finish before executing next line

Trying to work out how I can make the below code:
Wait for line 1 to complete before continuing.
Wait for line 4 to complete before running line 5
.
$invokevar = Get-ADComputer -Filter * -SearchBase $searchbase | select -Expand dnshostname
New-Variable -name "invoke$dom" -value $invokevar -Force
$fullvar = Get-Variable -Name "invoke$dom" -ValueOnly
$results = Invoke-Command -ComputerName $fullvar -ScriptBlock $sbmain
$badhosts = Compare-Object $($invokevar | Sort-Object) $($results | select -expand pscomputername | Sort-Object) | select -expand InputObject
Having a mental block, any help would be appreciated.
In powershell, the script executes line by line
Unless or until the execution of line 1 finishes, the script wont go for line 2.
So ideally you shouldn't be worrying about the problem stated above.
For internal commands PowerShell does wait before starting the next command. One exception to this rule is external Windows subsystem based EXE applications, you can apply out-null
PowerShell will wait until the exe process has been exited before continuing.
You can also use Start-Process with the -Wait parameter:
Start-Process <path to exe> -NoNewWindow -Wait
If you are using the PowerShell Community Extensions version it is:
$proc = Start-Process <path to exe> -NoWindow
$proc.WaitForExit()
Another option in PowerShell 2.0 is to use a background job:
$job = Start-Job { invoke command here }
Wait-Job $job
Receive-Job $job
In your case it will wait for the execution to get completed. Else you can check the status using a do-While loop and keep on adding a start-sleep of 1 sec
Hope this approach helps you.
Those answers are wrong. Get-ADUser absolutely may return data in the middle of the script down the line..
Some get-aduser command
echo "some string"
I have seen output line 2 first and then the results from line 1.
The only way around this is to assign a variable to the query and process the variable.
$string = get-aduser....
process $string
echo "some string"
This will process in order 1,2,3 without failure.

Persist current directory between PowerShell sessions

Is there a way to make PowerShell Console remember the current directory from the last time it was run and use that as the default location for the next instance?
eg.
Open PowerShell Console
Set-Location c:\tmp
Close PowerShell
Open PowerShell Console
Get-Location returns c:\tmp
You could change the prompt function to save location changes to a user environment variable:
$Function:prompt = {
if([System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User) -ne $PWD){
[System.Environment]::SetEnvironmentVariable("LocationMemory",$PWD,[System.EnvironmentVariableTarget]::User)
}
"PS $($ExecutionContext.SessionState.Path.CurrentLocation)$('>' * ($NestedPromptLevel + 1))"
}
And then check if the environment variable exists and is valid when powershell starts
if(($LastLocation = [System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User))){
if((Test-Path $LastLocation -PathType Container)){
Set-Location $LastLocation
}
}
Put both snippets in your profile to make it work. You probably want to limit it to $Profile.CurrentUserCurrentHost
I don't know if this is perfect but testing on my machine it seems to work. First check out this question/answer, this is the icing of the cake. You are basically going to have to log the current path of your session through an exiting event with PowerShell.
You can add this bit of code to your $PROFILE so it will always register the exit event and then set the path.
Register-EngineEvent PowerShell.Exiting -Action {(Get-Location).Path | Out-File 'C:\Users\wshaw\Documents\WindowsPowerShell\LastPath.txt' -Force}
$lastPath = 'C:\Users\wshaw\Documents\WindowsPowerShell\LastPath.txt'
if (Test-Path $lastPath) {
Set-Location (Get-Content $lastPath)
}
Thanks to #Shawn and #Mathias for both giving great answers. I ended up combining your approaches to come up with the following:
Register-EngineEvent PowerShell.Exiting -Action {
Write-Warning "Saving current location"
[System.Environment]::SetEnvironmentVariable("LocationMemory", (Get-Location).Path, [System.EnvironmentVariableTarget]::User)
} | Out-Null
$lastPath = [System.Environment]::GetEnvironmentVariable("LocationMemory",[System.EnvironmentVariableTarget]::User)
if (($lastPath -ne $null) -and (Test-Path $lastPath)) {
Set-Location $lastPath
}
There is around a 4 second delay for me when SetEnvironmentVariable is called, which is why I added the Write-Warning (otherwise you might think that the console window isn't closing after you exited or clicked on the Close Window control)

Count number of scripts running and wait for them to finish

I'm looking for the best way to count the number of PowerShell scripts that are currently running.
I run .ps1 scripts from windows batch files. The script I am working on now is launched when a particular email is received from a client - but I want this script to first of all check that no other scripts are busy running at the moment, and if they are it must wait for them to finish before it continues.
I'm sure there are a few ways to go about this, but what would be the safest? I am still learning.
If it is possible to move away from batch files to launch PowerShell then I would suggest using Start-Process to launch your scripts. This will allow you to wait for your processes to exit using where-object and Measure-Object to filter the scripts that have not yet completed.
So your script might look something like this:
# create a loop
foreach ($item in $reasontoloop) {
$arguments = "define script names and arguments"
# Start the powershell script
$procs += Start-Process powershell -PassThru -argumentlist $arguments
}
Write-Host -message "Waiting for Processes to complete"
while( $procs | Where-Object { $_.hasExited -eq $false } )
{
# Display progress
$measureInfo = $procs | Where-Object { $_.hasExited -eq $true } | Measure-Object
write-host "$($measureInfo.count) of $($procs.Length) still running"
Start-Sleep 1
}
Write-Host -message "Processes complete"
If you are simply interested in the number of PowerShell instances executing then the following one liner using Get-Process will help.
#(Get-Process | where-object {$_.ProcessName -like 'powershell'}).count

Powershell pipe into exe and wait

I am piping an array of data into a executable program but I need it to block after every call in the foreach loop. It will leave the loop before it even opens the program from the first call.
Set-Alias program "whatever.exe"
foreach ($data in $all_data)
{
$data| %{ program /command:update /path:"$_" /closeonend:2 }
}
I like PowerShell but I never really learned Invoke-Command. So whenever I need to run an EXE I always use cmd. If you type cmd /? you get its help, look at the "c" switch. I'd do something like this:
foreach ($data in $all_data){
$data |
Foreach-Object{
cmd /c "whatever.exe" /command:update /path:"$_" /closeonend:2
}
}
If you don't like the cmd /c thing you could use Jobs.
foreach ($data in $all_data){
$data |
Foreach-Object{
$job = Start-Job -InitializationScript {Set-Alias program "whatever.exe"} -ScriptBlock {program /command:update /path:"$($args[0])" /closeonend:2} -ArgumentList $_
while($job.Status -eq 'Running'){
Start-Sleep -Seconds 3
#Could make it more robust and add some error checking.
}
}
}
I can think of two ways to tackle this:
pipe your executable call to Out-Null
shell out the call to cmd.exe /c (as shown in #BobLobLaw's answer)
I made your sample code a little more specific so I could run and test my solutions; hopefully it'll translate. Here's what I started with to be equivalent to your sample code, i.e. the script executes with no waiting on the executable to finish.
# I picked a specific program
Set-Alias program "notepad.exe"
# And put some values in $all_data, specifically the paths to three text files.
$all_data = Get-Item B:\matt\Documents\*.txt
# This opens each file in notepad; three instances of notepad are running
# when the script finishes executing.
$all_data | %{ program "$_" }
Here's the same code as above, but piping to Out-Null forces the script to wait on each iteration of the loop.
# I picked a specific program
Set-Alias program "notepad.exe"
# And put some values in $all_data, specifically the paths to three text files.
$all_data = Get-Item B:\matt\Documents\*.txt
# Piping the executable call to out-null forces the script execution to wait
# for the program to complete. So in this example, the first document opens
# in notepad, but the second won't open until the first one is closed, and so on.
$all_data | %{ program "$_" | Out-Null}
And, lastly, the same code (more or less) using cmd /c to call the executable and make the script wait.
# Still using notepad, but I couldn't work out the correct call for
# cmd.exe using Set-Alias. We can do something similar by putting
# the program name in a plain old variable, though.
#Set-Alias program "notepad.exe"
$program = "notepad.exe"
# Put some values in $all_data, specifically the paths to three text files.
$all_data = Get-Item B:\matt\Documents\*.txt
# This forces script execution to wait until the call to $program
# completes. Again, the first document opens in notepad, but the second
# won't open until the first one is closed, and so on.
$all_data | %{ cmd /c $program "$_" }
Depending on your scenario, wait-job might be overkill. If you have a programmatic way to know that whatever.exe has done its thing, you could try something like
do {start-sleep -sec 2} until ($done -eq $true)
Oh and.