I am trying to run a Test-Path on a UNC path to a different server. While this works great for directories that exist, if it does not exist it attempts the connection for around 2 minutes. I am trying to see if I can cancel a Test-Path if it runs longer than a specified time. I found this code on stackoverflow, and it works for throwing the error, but when I run EndInvoke() it still takes the 2 minutes or so. Here is what I have:
$ps = [powershell]::Create().AddScript("Test-Path '\\SERVER_NAME\DirectoryDoesNotExist'")
$handle = $ps.BeginInvoke()
if(-not $handle.AsyncWaitHandle.WaitOne(2500)){ throw "timed out" return
}
$result = $ps.EndInvoke($handle) It works fine until $result = $ps.EndInvoke($handle). This takes several minutes to complete, making all the extra timeout logic redundant. Is there a way to get a successful timeout on a Test-Path?
$ps = [powershell]::Create().AddScript("Test-Path '\\SERVER_NAME\DirectoryDoesNotExist'")
$handle = $ps.BeginInvoke()
if(-not $handle.AsyncWaitHandle.WaitOne(5000)){ $boolExists = $false
} else { $boolExists = $true
}
$boolExists
if ($boolExists) { $result = $ps.EndInvoke($handle)
} else { $result = $boolExists
}
$resultThis blog explains setting a timeout in PowerShell scripts to prevent hanging.
There’s nothing worse than iterating through a list of commands and the whole workflow grinds to a halt because one command is hanging. But resolving the issue is not as trivial as it first seems.
“Use a PowerShell job!” is what I hear you shout. But this method exposes a couple of issues. The first is that if the job spawns another process (notepad.exe for example) and the job times out after the specified timeout period, we can remove the job but the notepad.exe process stays running! And we can’t get the process ID to kill it because to keep the job running, we generally need to use
Start-Process
with the
-Wait
and
-PassThru
parameters, and because the process hasn’t ended it won’t return the process object (which includes the Process ID and the Exit Code)!
The second issue is that if we don’t use the
-wait
parameter, the job will end instantly and it will return the process object where we can obtain the running process ID, but it won’t return an exit code because the process is still running!
There were other annoying complexities I found along the way too, but thankfully after hours of head scratching I found a solution.
I think the cmdlet aspect is fine. But for processes, I’m struggling. In the below example if I take away the -wait from running notepad, it returns a process ID (which i could then kill) but of course the job finishes instantly.
If I put the -wait in, when it times out it returns nothing because the start-process command hasn’t finished. So I can’t kill the launched process (notepad.exe).
Any pointers would be helpful – thanks.
$timeoutSeconds = 5
function Start-My-Process { param($ScriptBlock, $TimeoutSeconds) $job = Start-Job -ScriptBlock $ScriptBlock if($job | Wait-Job -Timeout $TimeoutSeconds){ $returnCode = $job | Receive-Job } else { write-host "timed out" #cannot get process ID to kill? #& taskkill /PID $returnCode /f /t } $job |Remove-Job -Force
}
$code = { #start-sleep -Seconds 3 return (start-process notepad.exe -passthru -wait).Id
}
Start-My-Process $code $timeoutSecondsasked Dec 20, 2023 at 15:00
1 gold badge14 silver badges32 bronze badges
- You don’t need to kill any console-application child process launched by direct invocation from your job explicitly – it is automatically killed as part of (forcefully) removing the enclosing job with
Remove-Job -Force.
cmd /c Notepad
Otherwise, use
Start-Processwith-PassThruto launch the application and output the resulting process-information object from the job, where the caller can retrieve it viaReceive-Job, and obtain the PID (process ID) from its.Idproperty.
You can also use this technique if you want to asynchronously launch console applications from your job (at the expense of being able to directly capture their output).
Finally, note that UWP applications (typically obtained from Microsoft Store) are often launched in a manner where the original process delegates to a different, non-child process and then exits right away, which makes them impossible to track based on the original process’ ID.
answered Dec 20, 2023 at 17:28
67 gold badges672 silver badges861 bronze badges
I frequently need to diagnose network connectivity issues and I’m currently using Test-NetConnection in PowerShell (Version 5.1). The output tells me if a port is reachable. In case it’s not, I would like to know the specific error, e.g. timeout or connection refused. So far, I couldn’t find this piece of information in the output.
Is it possible to get the specific error message for a failed connection attempt using Test-NetConnection? If so, how?
For reference, here are three examples of output that I get.
- Successful connection
Test-NetConnection somehostname -Port 1433 -InformationLevel Detailed
ComputerName : somehostname
RemoteAddress : 10.0.1.201
RemotePort : 1433
NameResolutionResults : 10.0.1.201
MatchingIPsecRules :
NetworkIsolationContext : Private Network
InterfaceAlias : Dialog
SourceAddress : 10.0.2.150
NetRoute (NextHop) : 10.0.2.99
TcpTestSucceeded : True- Same test from a different source with firewall in between
Test-NetConnection somehostname -Port 1433 -InformationLevel Detailed
WARNING: TCP connect to (10.0.1.201 : 1433) failed
ComputerName : somehostname
RemoteAddress : 10.0.1.201
RemotePort : 1433
NameResolutionResults : 10.0.1.201
MatchingIPsecRules :
NetworkIsolationContext : Private Network
IsAdmin : False
InterfaceAlias : Dialog
SourceAddress : 10.0.2.38
NetRoute (NextHop) : 10.0.2.35
PingSucceeded : True
PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded : False- Test closed port on localhost
Test-NetConnection localhost -Port 12345 -InformationLevel Detailed
WARNING: TCP connect to (127.0.0.1 : 12345) failed
WARNING: TCP connect to (::1 : 12345) failed
ComputerName : localhost
RemoteAddress : 127.0.0.1
RemotePort : 12345
NameResolutionResults : 127.0.0.1 ::1
MatchingIPsecRules :
NetworkIsolationContext : Loopback
InterfaceAlias : Loopback Pseudo-Interface 1
SourceAddress : 127.0.0.1
NetRoute (NextHop) : 0.0.0.0
PingSucceeded : True
PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded : FalseBackground
Inactivity timeouts aren’t just about security, they’re also about resource management. IT professionals and Managed Service Providers (MSPs) often implement these features to save power, reduce wear on hardware, and as an initial layer of security. The script provided dives deep into the realm of automating this process, allowing for a set-it-and-forget-it approach.
The Script
#Requires -Version 5.1
<#
.SYNOPSIS Set the Inactivity(Lock Computer) timeout time if it already isn't set.
.DESCRIPTION Set the Inactivity(Lock Computer) timeout time if it already isn't set. Can be set regardless if the -Force parameter is used.
.EXAMPLE -Minutes 5 This set the Inactivity(Lock Computer) timeout to 5 minutes, does not change if already set.
.EXAMPLE -Minutes 5 -Force This set the Inactivity(Lock Computer) timeout to 5 minutes, and forces the change if already set.
.EXAMPLE PS C:> Set-IdleLock.ps1 -Minutes 5 This set the Inactivity(Lock Computer) timeout to 5 minutes
.OUTPUTS None
.NOTES Minimum OS Architecture Supported: Windows 10, Windows Server 2016 Release Notes: Renamed script and added Script Variable support, updated Set-ItemProp
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
.COMPONENT LocalUserAccountManagement
#>
[CmdletBinding()]
param ( [Parameter()] [int]$Minutes, [switch]$Force = [System.Convert]::ToBoolean($env:force)
)
begin { if ($env:minutes -and $env:minutes -notlike "null") { $Minutes = $env:minutes } if(-not ($Minutes)){ Write-Error "Minutes is required!" exit 1 } if($Minutes -gt 9999 -or $Minutes -lt 0){ Write-Error "Minutes must be between 0 and 9999 (including 0 and 9999)." exit 1 } function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Output $true } else { Write-Output $false } } function Set-ItemProp { param ( $Path, $Name, $Value, [ValidateSet("DWord", "QWord", "String", "ExpandedString", "Binary", "MultiString", "Unknown")] $PropertyType = "DWord" ) # Do not output errors and continue $ErrorActionPreference = [System.Management.Automation.ActionPreference]::SilentlyContinue if (-not $(Test-Path -Path $Path)) { # Check if path does not exist and create the path New-Item -Path $Path -Force | Out-Null } if ((Get-ItemProperty -Path $Path -Name $Name)) { # Update property and print out what it was changed from and changed to $CurrentValue = Get-ItemProperty -Path $Path -Name $Name try { Set-ItemProperty -Path $Path -Name $Name -Value $Value -Force -Confirm:$false -ErrorAction Stop | Out-Null } catch { Write-Error $_ } Write-Host "$Path$Name changed from $CurrentValue to $(Get-ItemProperty -Path $Path -Name $Name)" } else { # Create property with value try { New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $PropertyType -Force -Confirm:$false -ErrorAction Stop | Out-Null } catch { Write-Error $_ } Write-Host "Set $Path$Name to $(Get-ItemProperty -Path $Path -Name $Name)" } $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Continue }
}
process { if (-not (Test-IsElevated)) { Write-Error -Message "Access Denied. Please run with Administrator privileges." exit 1 } $Path = "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystem" $IdleName = "InactivityTimeoutSecs" $Seconds = $Minutes * 60 # Override "Check if already set" if (-not $Force) { # Check if already set if ($(Get-ItemProperty -Path $Path | Select-Object -Property $IdleName -ExpandProperty $IdleName -ErrorAction SilentlyContinue)) { $CurrentIdleSeconds = $(Get-ItemPropertyValue -Path $Path -Name $IdleName) # If value already set, do nothing. if ($CurrentIdleSeconds) { exit 0 } } } # Sets InactivityTimeoutSecs to $Minutes try { Set-ItemProp -Path $Path -Name $IdleName -Value $Seconds Write-Host "Set the Inactivity to $($Seconds/60) minutes." } catch { Write-Error $_ exit 1 }
}
end {
}Access 300+ scripts in the NinjaOne Dojo
Detailed Breakdown
This PowerShell script aims to set the inactivity timeout on Windows. Here’s how it works:
- Prerequisites: The script starts by mentioning that it requires at least version 5.1 of PowerShell.
- Parameters: Two parameters are set – $Minutes, which is mandatory and denotes the duration after which the computer should lock, and $Force which, when used, forces the inactivity timeout even if already set.
Functions:
- Test-IsElevated: Checks if the script is run with administrator privileges.
- Set-ItemProp: Handles the creation or modification of the registry key.
Process Block:
- Initial Check: Verifies the user has the necessary privileges.
- Registry Path: Directs to the location where the inactivity timeout is set.
- Condition Check: If the -Force switch isn’t used, it checks if the timeout is already set. If set, it exits without making changes.
- Setting Timeout: If conditions pass, the inactivity timeout is set or changed.
Potential Use Cases
Imagine working in a university’s public computer lab. Multiple students use these computers throughout the day. To ensure each student’s work remains confidential, the IT department could utilize this script. By setting an inactivity timeout of, say, 5 minutes, they ensure that if a student forgets to log out, the system automatically locks, preventing unauthorized access.
Comparisons
While there are GUI methods to set inactivity timeouts, such as through the Windows Control Panel, this script provides an advantage in scalability. For massive deployments across numerous computers, using a script like this is more efficient. Additionally, other methods might involve Group Policy settings, but they lack the granularity and quick application that a PowerShell script can offer.
FAQs
- Why use PowerShell for this task?
PowerShell allows for automation, scalability, and quick deployment, especially across multiple systems. - Is admin privilege always required?
Yes, to modify system settings like inactivity timeouts, administrator privileges are essential. - Can the timeout be set for more extended periods?
This script allows setting the timeout for up to 9999 minutes, but it’s essential to strike a balance between usability and security.
Implications
While the immediate purpose is to enhance security, the wrong inactivity timeout can disrupt work, especially if set too short. On the flip side, too long a timeout might compromise security. Moreover, inactivity settings become critical in high-security environments, where leaving a terminal unlocked could have grave consequences.
Recommendations
- Test Before Applying: Always run scripts in a test environment before deploying widely.
- Balance Security with Usability: While shorter timeouts are more secure, they shouldn’t hinder daily tasks.
- Stay Updated: Periodically review and adjust the inactivity timeouts as per organizational needs.
Final Thoughts
NinjaOne offers a comprehensive IT monitoring and management solution, and when integrated with scripts like this, it provides robust control over an IT environment. Whether you’re looking to set inactivity timeouts or manage other aspects of IT infrastructure, NinjaOne remains a reliable partner for efficient, secure, and optimized operations.
Use PowerShell Script Blocks and Wait-Process for PowerShell Script Timeout
#function returns -1 if it times out. Otherwise the exit code of the scriptblock.
function Invoke-ScriptBlock {
param([string]$scriptBlock,[int]$timeoutSeconds)
#encode script to obfuscate
$encodedScriptBlock = [convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($scriptBlock))
$Arguments = "-Noexit", "-NoLogo", "–NoProfile", "-ExecutionPolicy Bypass", "-EncodedCommand $encodedScriptBlock"
$ProcessStartInfoParam = [ordered]@{
Arguments = $Arguments -join " "
FileName = "powershell.exe"
LoadUserProfile = $false
UseShellExecute = $false
CreateNoWindow = $true
}
$ProcessStartInfo = New-Object -TypeName "System.Diagnostics.ProcessStartInfo" -Property $ProcessStartInfoParam
$proc = New-Object "System.Diagnostics.Process"
$proc.StartInfo = $ProcessStartInfo
$proc.Start() | Out-Null
$timedOut = $null
$proc | Wait-Process -Timeout $timeoutSeconds -ErrorAction SilentlyContinue -ErrorVariable timedOut
if ($timedOut)
{
#timed out
$parentProcessId = $proc.Id
$parentProcessName = $proc.Name
foreach ($childProcess in (gwmi win32_process -Filter "ParentProcessId='$parentProcessId'")) {
$childProcessId = $childprocess.processid
$childProcessName = $childProcess.Name
& taskkill /PID $childProcessId /f /t /fi "STATUS eq RUNNING" 2>&1>$null
}
return -1
}
else
{
return $proc.ExitCode
}
}
#EXAMPLE 1 - SPAWNING A PROCESS
#Launch msiexec.exe. If we close it within 5 seconds it will return a 1639 exit code.
#If we leave the dialog open, it will time out and return -1
$sb = {
exit (start-process msiexec.exe -passthru -wait).ExitCode
}
$exitCode = Invoke-ScriptBlock $sb 5
write-host "Exit code was $exitCode"
#EXAMPLE 2 - RUNNING A CMDLET
#Sleep for 6 seconds
$sb = {
try {
start-sleep -seconds 6
exit 0
} catch {
exit -999
}
}
#change timeout to 5 seconds (less than sleep duration) to return -1 and a timeout
#change timeout to 7 seconds (more than the sleep duration) to return 0 and a success
$exitCode = Invoke-ScriptBlock $sb 7
write-host "Exit code was $exitCode"
#EXAMPLE 3 - PASS PARAMETERS TO SCRIPT BLOCK
#(Check output in c:\Alkane\Alkane.txt)
$first = "Alkane"
$last = "Solutions"
$params = -join ("Param(",
"[string]`$first = '$first',",
"[string]`$last = '$last'",
")")
$sb = $params + {
try {
"$first $last" | Out-File c:\Alkane\Alkane.txt
exit 0
} catch {
exit -999
}
}
$exitCode = Invoke-ScriptBlock $sb 5
write-host "Exit code was $exitCode"




