I have a script which is fetching the disk space information of many machines in a call center. However, sometimes this function takes too long. I need to create a time out for that function and have been unable to. I tried using start-job but to be honest, I don't fully understand it and so I am not getting the desired results.
try {
$timeoutSeconds = 30
$code = {
param($currentPCname, $activeDriveTypes)
// Function which takes computer name as input and outputs the ComputerName,DeviceID, Size, Freespace and DriveType (3 = local)
function get-FDS {
[cmdLetBinding()]
Param([string]$hostName)
Get-WmiObject Win32_LogicalDisk -ComputerName $hostName |
Where-object {$_.DriveType -in $activeDriveTypes} |
select-Object #{Name="ComputerName";Expression={$hostName}}, #{Name="Date";Expression={Get-Date}}, DeviceID, Size, Freespace, DriveType
}
get-FDS($currentPCname) -errorAction stop
}
$processTime = measure-command {
$j = Start-Job -ScriptBlock $code -Arg $currentPCname, $activeDriveTypes
if (Wait-Job $j -Timeout $timeoutSeconds) { $tempData = Receive-Job $j }
$jobState = $j.state
Remove-Job -force $j
}
if ($jobState -ne 'Completed') {
$pcTurnedOn = $false
$errorMessage = "ERROR talking to $currentPCname : Query timed-out"
$query = "CALL sp_fds_insert_log(" + $currentRunID + ", '" + $errorMessage + "', '" + $scriptName + "');"
$cmd = new-object mysql.data.mysqlclient.mysqlcommand($query, $conn)
$cmd.executenonquery()
}
} catch {
$pcTurnedOn = $false
$errorMessage = "ERROR talking to $currentPCname : $_"
$query = "CALL sp_fds_insert_log(" + $currentRunID + ", '" + $errorMessage + "', '" + $scriptName + "');"
$cmd = new-object mysql.data.mysqlclient.mysqlcommand($query, $conn)
$cmd.executenonquery()
}
The main point of the code above is that if the line below which is calling the $code segment
$processTime = measure-command {
$j = Start-Job -ScriptBlock $code -Arg $currentPCname, $activeDriveTypes
if (Wait-Job $j -Timeout $timeoutSeconds) { $tempData = Receive-Job $j }
$jobState = $j.state
Remove-Job -force $j
}
takes more than 30 seconds which is the $timeoutSeconds, the last IF statement would be called, if the line above does not work for some reason, the catch statement would be called and if it works in less than 30 seconds, nothing would be called.
If the job finished before the timeout is reached, Wait-Job will return the job itself - if the timeout is exceeded it won't return anything:
$timeout = 2
$job = Start-Job { Start-Sleep -Seconds 3 }
$done = $job |Wait-Job -TimeOut $timeout
if($done){
# It returned within the timeout
}
else {
# Nothing was returned, job timed out
}
Related
My Script has a lot of commands that taking time.
How to make powershell animation till the last commands runs. I don't want start-sleep
I tried Wait-Job but not working.
I just got this function and it uses Start-sleep I can change it if it will affect
function ProcessingAnimation($scriptBlock) {
$cursorTop = [Console]::CursorTop
try {
[Console]::CursorVisible = $false
$counter = 0
$frames = '|', '/', '-', '\'
$jobName = Start-Job -ScriptBlock $scriptBlock
while($jobName.JobStateInfo.State -eq "Running") {
$frame = $frames[$counter % $frames.Length]
Write-Host "$frame" -NoNewLine
[Console]::SetCursorPosition(0, $cursorTop)
$counter += 1
Start-Sleep -Milliseconds 125
}
# Only needed if you use a multiline frames
Write-Host ($frames[0] -replace '[^\s+]', ' ')
}
finally {
[Console]::SetCursorPosition(0, $cursorTop)
[Console]::CursorVisible = $true
}
}
Write-Host "Getting System Information..."
ProcessingAnimation { Wait-Job -Name $WindowsVer }
command1
command2
command3
command4
command5
command6
$WindowsVer = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").ReleaseId
Full disclosure: My problem may be based on a incomplete understanding of the Citrix PowerShell module for Xen Desktop.
I have the following script block. It's called in a loop, once for each VM in a list. I'm using PowerShell Jobs because I want to keep the UI thread free to update the UI while the jobs run.
Code "A"
$j = Start-Job -Name $jobName -ScriptBlock {
param($url, $uuid, $cred, $snapshotName)
$ErrorActionPreference = "Stop"
try
{
$error.clear()
$xSS = $cred | Connect-XenServer -url $url -NoWarnCertificates -SetDefaultSession -PassThru;
$vm = (Get-XenVM -SessionOpaqueRef $xss.opaque_ref -uuid $uuid)
#Create snapshot
Invoke-XenVM -Async -SessionOpaqueRef $xss.opaque_ref -VM $vm -XenAction Snapshot -NewName $snapshotName
return "OK"
}
catch
{
return ("ERROR: "+$error)
}
} -ArgumentList $global:configFileVmMetaData.poolUrl, $xenVm.key, $global:cred, $snapshotName
Code "A" works ok, but it takes longer then necessary because I'm executing the Connect-XenServer cmdlet each time I invoke the script block.
So, I tried calling Connect-XenServer once outside the loop and passing in the session variable as shown below in Code "B". The result was the error Could not find open sessions to any XenServers being thrown inside the script block. I'm assuming that the $xss session variable is getting whanged somehow when its passed into the script block.
Any ideas why $xss session variable is getting whanged?
Code "B"
$xSS = $cred | Connect-XenServer -url $global:configFileVmMetaData.poolUrl -NoWarnCertificates -SetDefaultSession -PassThru;
loop
{
$j = Start-Job -Name $jobName -ScriptBlock {
param($xss, $uuid, $snapshotName)
$ErrorActionPreference = "Stop"
try
{
$error.clear()
$vm = (Get-XenVM -SessionOpaqueRef $xss.opaque_ref -uuid $uuid)
#Create snapshot
Invoke-XenVM -Async -SessionOpaqueRef $xss.opaque_ref -VM $vm -XenAction Snapshot -NewName $snapshotName
return "OK"
}
catch
{
return ("ERROR: "+$error)
}
} -ArgumentList $xss, $xenVm.key, $snapshotName
}
Additional examples inspired by the Robert Cotterman answer
In all cases, I still get the Could not find open sessions to any
XenServers error
FYI - Using PowerShell 5.1
Example using $using. Variable contents are passed in and back out as expected
cls
$aLocal = "AAA"
$bLocal = "BBB"
$j = Start-Job -Name "TestJob" -ScriptBlock {
return ($using:aLocal + " *** " + $using:bLocal)
}
while ($true)
{
$g = get-job -name "TestJob"
write-host ("get-job " + $g.Name + " is " + $g.State)
if ($g.State -ne "Running")
{
break
}
start-sleep -Seconds 1
}
write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")
$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)
if($g)
{
Remove-Job -Name "TestJob"
}
Output
get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 45
Remove-Job
Example using args. Variable contents are passed in and back out as expected
cls
$aLocal = "AAA"
$bLocal = "BBB"
$j = Start-Job -Name "TestJob" -ScriptBlock {
return ($args[0] + " *** " + $args[1])
} -ArgumentList ($aLocal, $bLocal)
while ($true)
{
$g = get-job -name "TestJob"
write-host ("get-job " + $g.Name + " is " + $g.State)
if ($g.State -ne "Running")
{
break
}
start-sleep -Seconds 1
}
write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")
$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)
if($g)
{
Remove-Job -Name "TestJob"
}
Output
get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 49
Example using named args. Variable contents are passed in and back out as expected
cls
$aLocal = "AAA"
$bLocal = "BBB"
$j = Start-Job -Name "TestJob" -ScriptBlock {
param($a, $b)
return ($a + " *** " + $b)
} -ArgumentList ($aLocal, $bLocal)
while ($true)
{
$g = get-job -name "TestJob"
write-host ("get-job " + $g.Name + " is " + $g.State)
if ($g.State -ne "Running")
{
break
}
start-sleep -Seconds 1
}
write-host ("receive-Job='" + (receive-Job -Name "TestJob") +"'")
$g = get-Job -Name "TestJob"
Write-Host ("get-Job "+$g.name + " " + $g.state + " " + $g.HasMoreData + " " + $g.id)
if($g)
{
Remove-Job -Name "TestJob"
}
Output
get-job TestJob is Running
get-job TestJob is Completed
receive-Job='AAA *** BBB'
get-Job TestJob Completed False 55
I just don't think it works that way. The connection to the Xen server is created for your PowerShell session, with info about that connection collected in the $xss variable. Each job runs its own PowerShell session. So just passing $xss to the job isn't the same, it's just a description of the connection details.
Jobs and invoke-command require you to specify that you're using your variable. Simply change the variable from
$variable
To
$using:variable
Internal variables do not need this. But calling upon variables from the parent script do.
Alternatively, Since you're passing $xss as an argument, you wouldn't call it with $xss but rather
$args[0]
Since it's your first argument. And $args[1] for the second etc...
The reason is because the entire xss variable is being printed as an argument and is not named inside the job. It's given the name $args and has a place in the first slot (0).
I prefer $using:variable as it reduces confusion
Long story short, we are experiencing issues with some of our servers that cause crippling effects on them and I am looking for a way to monitor them, now I have a script that will check the RDP port to make sure that it is open and I am thinking that I want to use get-service and then I will return if it pulled any data or not.
Here is the issue I don't know how to limit the time it will wait for a response before returning false.
[bool](Get-process -ComputerName MYSERVER)
Although I like Ansgars answer with a time-limited job, I think a separate Runspace and async invocation fits this task better.
The major difference here being that a Runspace reuses the in-process thread pool, whereas the PSJob method launches a new process, with the overhead that that entails, such as OS/kernel resources spawning and managing a child process, serializing and deserializing data etc.
Something like this:
function Timeout-Statement {
param(
[scriptblock[]]$ScriptBlock,
[object[]]$ArgumentList,
[int]$Timeout
)
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
$PS = [powershell]::Create()
$PS.Runspace = $Runspace
$PS = $PS.AddScript($ScriptBlock)
foreach($Arg in $ArgumentList){
$PS = $PS.AddArgument($Arg)
}
$IAR = $PS.BeginInvoke()
if($IAR.AsyncWaitHandle.WaitOne($Timeout)){
$PS.EndInvoke($IAR)
}
return $false
}
Then use that to do:
$ScriptBlock = {
param($ComputerName)
Get-Process #PSBoundParameters
}
$Timeout = 2500 # 2 and a half seconds (2500 milliseconds)
Timeout-Statement $ScriptBlock -ArgumentList "mycomputer.fqdn" -Timeout $Timeout
You could run your check as a background job:
$sb = { Get-Process -ComputerName $args[0] }
$end = (Get-Date).AddSeconds(5)
$job = Start-Job -ScriptBlock $sb -ArgumentList 'MYSERVER'
do {
Start-Sleep 100
$finished = (Get-Job -Id $job.Id).State -eq 'Completed'
} until ($finished -or (Get-Date) -gt $end)
if (-not $finished) {
Stop-Job -Id $job.Id
}
Receive-Job $job.Id
Remove-Job $job.Id
This is a known issue: https://connect.microsoft.com/PowerShell/feedback/details/645165/add-timeout-parameter-to-get-wmiobject
There is a workaround provided Here : https://connect.microsoft.com/PowerShell/feedback/details/645165/add-timeout-parameter-to-get-wmiobject
Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15)
{
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace
#write-host $assembledpath -foregroundcolor yellow
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()
$querystring = "SELECT * FROM " + $class
#write-host $querystring
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
You can call the function like this:
get-wmicustom -class Win32_Process -namespace "root\cimv2" -computername MYSERVER –timeout 1
$fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
If $winxp cannot be found, the command will hang, is there a timeout I can use with this to make it move on after 5-10 seconds? Not sure where I would put it.
Edit- I use this to pull the username:
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $tag1)
$key = $reg.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon')
$winxp = $key.GetValue('DefaultUserName') -replace '^.*?\\'
$winxp is then a login name such as ajstepanik then I put it into: $fullnamexp = ((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim();
1.21.2014 Update
$timeoutSeconds = 5
$code = {
((net user $winxp /domain | Select-String "Full Name") -replace "Full Name","").Trim(); # your commands here, e.g.
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { $fullnamexp = Receive-Job $j }
Remove-Job -force $j
While #mjolinor may have indeed provided you an alternative approach, here is a direct answer to your general question: how do you force a timeout in PowerShell?
Wrap whatever you wish to time-limit in a script block, run that as a job, then use the Wait-Job cmdlet to time-limit the operation. Wait-Job will return either at the end of the timeout period or when the script block completes, whichever occurs first. After Wait-Job returns, you can examine the job state ($j.state) to determine whether it was interrupted or not, if it matters to you.
$timeoutSeconds = 5 # set your timeout value here
$j = Start-Job -ScriptBlock {
# your commands here, e.g.
Get-Process
}
"job id = " + $j.id # report the job id as a diagnostic only
Wait-Job $j -Timeout $timeoutSeconds | out-null
if ($j.State -eq "Completed") { "done!" }
elseif ($j.State -eq "Running") { "interrupted" }
else { "???" }
Remove-Job -force $j #cleanup
2014.01.18 Update
Here is a bit more streamlining approach that also includes the practical step of getting information out of the script block with Receive-Job, assuming what you want is generated on stdout:
$timeoutSeconds = 3
$code = {
# your commands here, e.g.
Get-ChildItem *.cs | select name
}
$j = Start-Job -ScriptBlock $code
if (Wait-Job $j -Timeout $timeoutSeconds) { Receive-Job $j }
Remove-Job -force $j
You can use Start-Sleep to pause the script:
Start-Sleep -s 5
net doesn't explicitly allow you to set a time out on it's operations, but you could check out this link on changing the ipv4 timeout for your sockets:
http://www.cyberciti.biz/tips/linux-increasing-or-decreasing-tcp-sockets-timeouts.html
The only thing else I could imagine is spawning a worker thread but I don't even know if that's possible in bash, I'm not fluid enough in it to answer that; plus it opens you up to sync problems and all sorts of multi threaded issues beyond what you're trying to accomplish quickly in a bash script to begin with! :P
Does this help?
$query = (dsquery user -samid $winxp)
if ($query) {$fullnamexp = ($query | dsget user -display)[1].trim()}
$fullnamexp
This solution doesn't work for me. remove-job -force $j takes over 5 seconds in this example.
$timeoutseconds = 1
$start = get-date
$j = start-job -scriptblock { Resolve-DnsName 1.1.1.1 }
if (wait-job $j -timeout $timeoutseconds) { $fullnamexp = receive-job $j }
remove-job -force $j
(get-date) - $start
Days : 0
Hours : 0
Minutes : 0
Seconds : 5
Milliseconds : 342
Ticks : 53426422
TotalDays : 6.18361365740741E-05
TotalHours : 0.00148406727777778
TotalMinutes : 0.0890440366666667
TotalSeconds : 5.3426422
TotalMilliseconds : 5342.6422
Here's a simple timeout example with notepad:
notepad
if (-not $(wait-process notepad 10; $?)) { stop-process -name notepad }
$watchdog = 10 #seconds
$start_time = Get-Date
$j = Start-Job -ScriptBlock{
#timeout command
if ($true) {
$i = 0
while($true) {
Write-Host "Count: $i"
Start-Sleep -Milliseconds 100
$i++
}
}
write-host "Hello"
}
while($true) {
if ($j.HasMoreData) {
Receive-Job $j
Start-Sleep -Milliseconds 200
}
$current = Get-Date
$time_span = $current - $start_time
if ($time_span.TotalSeconds -gt $watchdog) {
write-host "TIMEOUT!"
Stop-Job $j
break
}
if (-not $j.HasMoreData -and $j.State -ne 'Running') {
write-host "Finished"
break
}
}
Remove-Job $j
I'm new to powershell. I read some lines on www.powershell.com. Now I need your help to solve a problem. I want to read the UUID from clients in the Network. Therefore I created a document "pcs.txt" where all PCs are stored.
$pc = Get-Content pcs.txt #Read content of file
$cred = Get-Credential “domain\user”
for ($i=0; $i -lt $pc.length; $i++) {
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$uuid = (Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred).UUID
$Ausgabe=$pc[$i] + ';'+$uuid
$Ausgabe
}
else
{
$Ausgabe=$pc[$i] + '; UUID nicht erhalten'
$Ausgabe
}
}
First I test if the ping works. When the ping works I try to get the uuid.
Sometimes I don't get the uuid even if the ping worked. So I would like to code a timeout, which say -> go to next pc when you don't have the uuid after 2 seconds.
Can you help me please?
Alas, there is no timeout parameter for Get-WmiObject commandlet. There is a feature request in MS Connect, but it is from 2011 and still open.
A workaround, which I haven't tested is available by using System.Management. I'll copy-and-paste it here in case the link goes dead. (And I hate SO answers that only contain links to resouces that may or may not exist...)
Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15){
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace
#write-host $assembledpath -foregroundcolor yellow
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()
$querystring = "SELECT * FROM " + $class
#write-host $querystring
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
I found a good workaround!
http://theolddogscriptingblog.wordpress.com/2012/05/11/wmi-hangs-and-how-to-avoid-them/
Here my working code:
$pc = Get-Content pcs.txt #FILE FROM THE HARDDISK
$cred = Get-Credential “DOMAIN\USER” #
for ($i=0; $i -lt $pc.length; $i++)
{
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$WMIJob = Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred -AsJob
$Timeout=Wait-Job -ID $WMIJob.ID -Timeout 1 # the Job times out after 1 seconds.
$uuid = Receive-Job $WMIJob.ID
if ($uuid -ne $null)
{
$Wert =$uuid.UUID
$Ausgabe=$pc[$i] + ';'+$Wert
$Ausgabe
}
else
{
<#$b = $error | select Exception
$E = $b -split (:)
$x = $E[1]
$Error.Clear() #>
$Ausgabe=$pc[$i] + '; got no uuid'
$Ausgabe
}
}
else
{
$Ausgabe='PC not reached through ping.'
$Ausgabe
}
}
I hope I can help somebody with that