As part of a Powershell script I need to perform a task that is typically quick, but can sometimes take a long amount of time. I want to execute the task, then wait either for it fo finish or for set time to pass, whichever happens first. When either condition happens, I need the Powershell script to return back to the command prompt.
Here is the closest I've come up with (using ping as an example)
$x = Start-Process -Filepath "ping" -ArgumentList 'google.com -n 100' -NoNewWindow -PassThru;
Start-Sleep -Seconds 5;
try { Stop-Process -Id $x.Id -ErrorAction stop } catch {};
This will kill the process after the timoeout is reached (if it is still running) and return back to the prompt. However, it won't return to the prompt if the command successfully completes before timeout. This results in the script always taking however long is specified in the timeout value.
The desired semantics are similar or identical to Linux's timeout command.
Requirements:
If the task completes within timeout window, control returns to the script (a prompt is displayed)
If the timeout is reached and the task is stil running, the task is killed and control returns to the script (a prompt is displayed)
Output from the task must be printed/displayed to stdout
Works over an SSH connection
Edited to use ping instead of notepad. I'm combining wait-process and "$?" into one statement with "$( )", because powershell "if" looks at the output, not the exit status.
start ping 'google.com -n 100'
if (-not $(wait-process ping 10; $?)) {
stop-process -name ping }
I think some of the requirement can be met by using a Stopwatch, you'll have to test the SSH connection.
$timeOut = New-TimeSpan -Seconds 5
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew()
do {
try {
Test-Connection -ComputerName google.ca -Count 1 -ErrorAction Stop
$timeOut = New-TimeSpan -Seconds 0 #Stop the stopWatch, we got a response
}
catch { #Write-Host "No response" }
}
While ($stopWatch.elaspsed -lt $timeOut)
Related
I need to kill the process if start time is less than 2 hours.
I need to add sleep for 30 mins if start time is more than 2 hours.
I need to keep repeating it until the process is no more running.
I have written the below script so far to perform the above action.
$procName = 'myprocess'
$process = Get-Process | Where-Object Name -EQ $procName
if(-not $process) {
Write-Warning "$procName not found!"
}
else {
$process | ForEach-Object {
if($_.StartTime -lt [datetime]::Now.AddHours(-2)) {
Stop-Process $_ -Force
}
else {
sleep(1800)
}
}
}
}
How to add the above program in a do-while or another loop so as to keep checking until the process is no more running?
Also, how to implement a maximum timer of 4 hours?
If I understood correctly, your else condition could look like this using a do-while loop:
else {
do {
"$procName is still running, sleeping for 1800 sec"
Start-Sleep -Seconds 1800
} while(Get-Process | Where-Object Name -EQ $procName)
}
However note that this could cause an infinite loop if the process never stops or you implement a maximum timer, etc.
Following your comment regarding implementing a maximum timer, there are many ways you could do it, my personal preference would be to use a StopWatch:
else {
$timer = [System.Diagnostics.Stopwatch]::StartNew()
do {
# Do this while the process is still running AND
# the timer hasn't yet reached 4 hours
"$procName is still running, sleeping for 1800 sec"
Start-Sleep -Seconds 1800
$stillRunning = Get-Process | Where-Object Name -EQ $procName
} while($stillRunning -and $timer.Elapsed.Hours -lt 4)
$timer.Stop()
}
I suggest you use a windows schedule task that launch your powershell script every 30 minutes or so instead of blocking resource with your powershell that is waiting.
You can launch powershell and pass a script.
PowerShell.exe -File "C:\script.ps1"
I am limited to only using native windows tools, and I need to automate opening a browser, going to a website, and then closing the browser. I'm going to use task scheduler to run the script.
I created this PowerShell script which successfully opens a browser, and closes it.
How would I go about having this open a specific webpage? Start Arguments or something else?
$browser = [Diagnostics.Process]::Start("chrome.exe")
$id = $browser.Id
Start-Sleep -Seconds 5
try {
Stop-Process -Id $id -ErrorAction stop
} catch {
Write-Host "Failed to kill"
}
You can start the process with the appropriate Chromium command line argument(s):
$browser = [Diagnostics.Process]::Start("chrome.exe", "https://stackoverflow.com/ --new-window")
$id = $browser.Id
Start-Sleep -Seconds 5
try {
Stop-Process -Id $id -ErrorAction stop
}
catch {
Write-Host "Failed to kill"
}
Process.Start Method
Run Chromium with flags
List of Chromium Command Line Switches
When I use start-process to create a new process and assign it to a variable...
$np = start-process -passThru notepad
... and then query .MainWindowHandle ...
$np.MainWindowHandle
... I seem to be given the HWND of the notepad.
However, when I try to do the same thing in one go...
(start-process -passThru notepad).MainWindowHandle
... I am given 0.
This is probably the case because MainWindowHandle is evaluated before notepad has created its window.
So, is there a way, without using start-sleep or going into a loop that repeadetly queries the value of MainWindowHandle, to wait until notepad is done starting up?
So, is there a way,
Yes
without using start-sleep or going into a loop that repeadetly queries
the value of MainWindowHandle
Not that I can think of :)
# Define a timeout threshold 10 seconds into the future
$threshold = (Get-Date).AddSeconds(10)
# Start the process
$proc = Start-Process notepad -PassThru
while(-not $proc.HasExited -and ((Get-Date) -lt $threshold -or $proc.MainWindowTitle -eq 0)){
Start-Sleep -MilliSeconds 250
}
if($proc.MainWindowTitle -eq 0){
if(-not $proc.HasExited)
$proc.Terminate()
}
throw 'Failed to spawn window in time'
return
}
# Do stuff with $proc.MainWindowHandle
Have a command that (on 2008 boxes) throws an exception due to the buffer limit.
Essentially, the script stops WAUA, blows out SoftwareDistribution, then reaches out to rebuild SD and check for updates against WSUS then checks back in so the alerts in WSUS stop.
I want a specific line to retry if an exception is thrown until it finishes without an exception, then we can tell it to report back in to WSUS to clear it out.
Stop-Service WUAUSERV
Remove-Item -Path C:\WINDOWS\SoftwareDistribution -recurse
Start-Service WUAUSERV
GPUpdate /force
WUAUCLT /detectnow
sleep 5
## This is the command I'd like to loop, if possible, when an exception is thrown ##
$updateSession = new-object -com "Microsoft.Update.Session"; $updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates
WUAUCLT /reportnow
Any kind of help would be appreciated. Everything I've been able to find has been how to create my own exception but not how to handle it when an exception is thrown and retry until it finishes without an error.
Edit:
Then based on the below Answer, is this how I would want it to be written so it will continue to check until it runs successfully and then it'll report back in?
Stop-Service WUAUSERV
Remove-Item -Path C:\WINDOWS\SoftwareDistribution -recurse
Start-Service WUAUSERV
GPUpdate /force
WUAUCLT /detectnow
sleep 5
while(-not $?) {$updateSession = new-object -com "Microsoft.Update.Session"; $updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates}
WUAUCLT /reportnow
You can use the special character $? This will return false if the last command returned with error, so your while loop would just look like:
while(-not $?)
See what is $? in powershell.
Alternatively, $error[0] gives the last thrown error message so you could build a while loop around that, similar to:
while($error[0] -ne "error message")
Set a value to false and only flip it if you get total success. Loop until you succeed or your timeout exceeds your defined value -- if you don't include a timeout, you have created an infinite loop condition where if the host never succeeds, it'll run until reboot.
$sts = $false
$count = 0
do {
try {
$updateSession = new-object -com "Microsoft.Update.Session"
$updates=$updateSession.CreateupdateSearcher().Search($criteria).Updates
$sts = $true
} catch {
## an exception was thrown before we got to $true
}
$Count++
Start-Sleep -Seconds 1
} Until ($sts -eq $true -or $Count -eq 100)
Let's say I have a script:
write-host "Message.Status: Test Message Status";
I managed to run it in a separate process by doing:
powershell.exe -Command
{ write-host "Message.Status: Test Message Status"; }
The problem is I want to pass parameters to the script so that I can achieve something like this:
write-host "I am in main process"
powershell.exe -Command -ArgumentList "I","am","here"
{
write-host "I am in another process"
write-host "Message.Status: $($one) $($two) $($three)";
}
However -ArgumentList doesn't work here
I get:
powershell.exe : -ArgumentList : The term '-ArgumentList' is not recognized as the name of a cmdlet, function, script file, or operable
I need to run some part of PowerShell script file in a different process and I cannot use another file due to the fact that PowerShell script is uploaded to external system.
The -Command parameter is expecting a scriptblock in which you can define your parameters using a Param() block. Then use the -args parameter to pass in the arguments. Your only mistake was to put the -args after -command before you defined the scriptblock.
So this is how it works:
write-host "I am in main process $($pid)"
powershell.exe -Command {
Param(
$one,
$two,
$three
)
write-host "I am in process $($pid)"
write-host "Message.Status: $($one) $($two) $($three)";
} -args "I", "am", "here" | Out-Null
Output:
I am in main process 17900
I am in process 10284
Message.Status: I am here
You can use the -File parameter and follow it by the path to script. Any unnamed arguments which follows will be passed as script parameters. Something like below should do
powershell -File "C:\ScriptFolder\ScriptwithParameters.ps1" "ParameterOneValu" "valuetwo"
Ok so if you need another process entirely but not another file then your best bet is probably .NET runspaces. Basically wrap your code in a scriptblock
$SB = {
*Your Code*
}
Then set up a runspace like below, making sure to use the "UseNewThread" as the thread option. Note that $arg is whatever your argument to be passed to the script is
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "UseNewThread"
$newRunspace.Open()
$psCmd = [PowerShell]::Create().AddScript($SB).AddArgument($arg)
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
You'll likely need to tweak this if you need to get any data back from the runspace once it is complete but there are a few ways to do that(leave a comment if you need assistance). If you need synchronous execution rather than async then change .BeginInvoke() to .Invoke()
So should get you started, But it will require a few moving parts.
First we define a new function:
function Run-InNewProcess{
param([String] $code)
$code = "function Run{ $code }; Run $args"
$encoded = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes($code))
start-process PowerShell.exe -argumentlist '-noExit','-encodedCommand',$encoded
}
This function will be what starts the new process. It uses the start-process cmdlet, The -Argumentlist is our arguments applied to the powershell.exe You can remove -noExit to make the new process close on completion or add other powershell flags, and flags on Start-Process to get the windows and behaviours tweaked to your requirements.
Next we define our script block:
$script = {
[CmdletBinding()]
Param (
[Parameter(Position=0)]
[string]$Arg1,
[Parameter(Position=1)]
[string]$Arg2)
write-host "I am in another process"
write-host "Message.Status: $($Arg1) $($Arg2)";
}
Here we define some parameters in the opening part of the block, They have a position and name, so for example any argument in position 0 will be in the variable $arg1 The rest of the code in the block is all executed in the new process.
Now we have defined the script block and the function to run it in a new process, All we have to do is call it:
Run-InNewProcess $script -Arg1 '"WHAT WHAT"' -Arg2 '"In the But"'
Copy past this code all in to your ISE and you will see it in action.
Start-Job will create a process for its scriptblock, and it's straightforward to pass arguments to it.
Write-Host "Process count before starting job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
$job = Start-Job `
-ArgumentList "My argument!" `
-ScriptBlock {
param($arg)
Start-Sleep -Seconds 5;
Write-Host "All Done! Argument: $arg"
}
while ($job.State -ne "Completed")
{
Write-Host "Process count during job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
Start-Sleep -Seconds 1
}
Receive-Job $job -AutoRemoveJob -Wait
Write-Host "Process count after job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"