Ad Space here

Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Share
Options
Go to last post Go to first unread
Dustin Higgins  
#1 Posted : Sunday, December 08, 2019 4:11:18 PM(UTC)
Dustin Higgins

Rank: Advanced Member

Groups: Registered
Joined: 7/1/2018(UTC)
Posts: 64
_United States

Thanks: 1 times
Was thanked: 6 time(s) in 6 post(s)

PowerShell, and Process with a Timeout

In this post, I’m going to demonstrate how to start a process in PowerShell through .NET, redirect the StdOut/StdErr streams,  and set a fail-safe timeout.   I’ve used this method over the years to start a process with a purpose - literally side by side in a lifeboat, trying to save a system from sinking. In other words, starting a process to quickly solve the "problem of the day" on many endpoints.

​​​​​​​

We all work in environments where a little housekeeping needs to be done.  There are times when this housekeeping isn’t done as well as it should be (or even at all).  There are things such as name resolution, intermittent or inconsistent connectivity, and hardware issues to name a few, that hinder the ability to quickly access endpoints.  Some endpoints such as remote, hard to access laptops are really challenging.  The timeout is really the key to the efficiencies.  There is no need to wait for RPC Errors to timeout, etc.  Threading this method becomes incredibly fast.

The example I’m going to show below will chunk through a list of endpoints very quickly, regardless of their state.  There are many applications for starting a process and reading the standard output; remote command line administration is just one of them.  I’ve recently used this to parse output from the “query /user:<server>” command to gain situational awareness for who is logged into servers.  It is really nice not having to redirect standard output to a file to read it.

Here is the code:

Code:
function Start-ProcessWaitTimeout
{
    <#
    .SYNOPSIS
    Function to start a process and wait until it completes or reaches a specified time limit in seconds
    .DESCRIPTION
    Function to start a process and wait until it completes or reaches a specified time limit in seconds
    #>
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Computer,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$CmdLine,
        [Parameter(ValueFromPipelineByPropertyName)]
        [array]$CmdLineArgs,
        [Parameter(Mandatory=$true)]
        [int]$Timeout
    )
    Begin {
        # Initialize list to hold the process objects
        $ProcessList = New-Object System.Collections.Generic.List[PSObject]
        # Establish Functions to Create and Wait for the Process
        function New-Process
        {
            <#
            .SYNOPSIS
            Create a process   
            .DESCRIPTION
            Create a process
            #>
            param (
                [Parameter(Mandatory=$true)]
                [string]$CmdLine,
                [Parameter(Mandatory=$true)]
                $CmdLineArgs
            )
            ## SET THE PROCESS INFO
            $processInfo = New-Object System.Diagnostics.ProcessStartInfo
            $processInfo.FileName = $cmdLine
            $processInfo.RedirectStandardError = $true
            $processInfo.RedirectStandardOutput = $true
            $processInfo.UseShellExecute = $false
            ## SET THE PROCESS
            $processInfo.Arguments = $cmdLineArgs
            $process = New-Object System.Diagnostics.Process
            $process.StartInfo = $processInfo
            # Return the Process
            Return $process
        }
        function Wait-ForProcess
        {
            <#
            .SYNOPSIS
            Wait for a process to complete.
            .DESCRIPTION
            Wait for a process to complete.
            #>
            param (
                [Parameter(Mandatory=$true)]
                [System.Diagnostics.Process]$Process,
                [Parameter(Mandatory=$true)]
                [int]$TimeoutSeconds
            )          
            $ProcessComplete = $Process.WaitForExit($TimeoutSeconds * 1000)
            if ($ProcessComplete -eq $false)
            {              
                $Process.Kill()
                Return -1
            } else {
                Return 0
            }          
        }
    } # End Begin
    Process {
        # Create the Process Object
        $ProcessObj = [PSCustomObject]@{        
            Computer = $computer
            CmdLine = $cmdLine
            CmdLineArgs = $cmdLineArgs
            Timeout = $timeout
            ProcessStartTime = $null
            ProcessEndTime = $null
            ProcessDuration = $null
            ProcessStdOut = $null
            ProcessStdErr = $null
            ProcessExitCode = $null
            ProcessResult = $null
            Result = $null
            ObjTimeStamp = (get-date)
        }
        # Create the process          
        $Process = New-Process -CmdLine $CmdLine -CmdLineArgs $CmdLineArgs
        # START THE PROCESS
        $Process.Start() | Out-Null
        $ProcessObj.ProcessStartTime = $process.StartTime
        # READ STD OUT AND ERROR TO END ASYNC SO BUFFER DOESNT FILL AND HANG PROCESS
        $stdOut = $process.StandardOutput.ReadToEndAsync()
        $stdErr = $process.StandardError.ReadToEndAsync()
        # WAIT for the Process to Complete
        $WaitProcess = Wait-ForProcess $Process $Timeout
        if ($WaitProcess -eq 0) {
            $processObj.ProcessResult = $Process.ProcessExitCode
        }
        if ($WaitProcess -eq -1) {
            $processObj.ProcessResult = "Timeout"
        }
        ## SET THE EXIT CODE STD OUT/ERR, END TIME, DURATION
        $processObj.ProcessExitCode = $process.ExitCode
        $processObj.ProcessStdOut = $stdOut.Result
        $processObj.ProcessStdErr = $stdErr.Result
        $processObj.ProcessEndTime = $process.ExitTime
        $processObj.ProcessDuration = $processObj.ProcessEndTime - $processObj.ProcessStartTime
        ## RETURN THE PROCESS OBJECT
        $ProcessList.Add($processObj)
    }

    End {         # Return the Process List         Return $ProcessList     } }

$Obj1 = [PSCustomObject]@{             Computer = "localhost"     CmdLine = "C:\Windows\System32\cmd.exe"     CmdLineArgs = @("/c","copy","/y","\\Computer1\c$\Windows\SoftwareDistribution\ReportingEvents.Log","$($env:TEMP)\localhost_ReportingEvents.log") }

$Obj2 = [PSCustomObject]@{             Computer = "Computer2"     CmdLine = "C:\Windows\System32\cmd.exe"     CmdLineArgs = @("/c","copy","/y","\\Computer2\c$\Windows\SoftwareDistribution\ReportingEvents.Log","$($env:TEMP)\Computer2_ReportingEvents.log") }

$Obj1, $Obj2 | Start-ProcessWaitTimeout -Timeout 2

This is my hammer.

Feedback is welcomed and follow Dustin Higgins on Twitter

DHB

Edited by user Tuesday, December 10, 2019 3:45:23 PM(UTC)  | Reason: Not specified

Sponsor
Ad Space here
Rss Feed  Atom Feed
Users browsing this topic
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.