Public/Start-Psexec.ps1

function Start-Psexec {
    <#
    .SYNOPSIS
        Starts an interactive PsExec session with automatic installation and setup.
 
    .DESCRIPTION
        Provides a simplified interface for starting PsExec sessions with automatic download
        and installation if PsExec is not found. Optimized for local SYSTEM context testing
        of Intune packages and applications. Defaults to PowerShell in SYSTEM context for
        most common System Administrator use cases.
 
    .PARAMETER Command
        Specifies the shell to launch:
        - powershell: Interactive PowerShell session (default)
        - cmd: Interactive Command Prompt session
 
    .PARAMETER SessionType
        Specifies the type of session to start:
        - System: Interactive session as NT AUTHORITY\SYSTEM (default)
        - Interactive: Interactive session as current user
        - UserContext: Interactive session with specified credentials
        - NetworkService: Interactive session as NetworkService account
 
    .PARAMETER ComputerName
        Target computer name for remote sessions. Optional - defaults to local computer.
 
    .PARAMETER Credential
        PSCredential object for UserContext sessions or remote computer authentication.
 
    .PARAMETER WorkingDirectory
        Starting directory for the session. Defaults to C:\Windows\Temp for SYSTEM context.
 
    .PARAMETER Force
        Forces re-download of PsExec even if already available.
 
    .PARAMETER Remove
        Removes all PsExec installations from the system.
 
    .EXAMPLE
        Start-Psexec
        Starts interactive PowerShell as SYSTEM on local computer (most common use case).
 
    .EXAMPLE
        Start-Psexec cmd
        Starts interactive Command Prompt as SYSTEM on local computer.
 
    .EXAMPLE
        Start-Psexec -SessionType Interactive
        Starts interactive PowerShell session as current user.
 
    .EXAMPLE
        Start-Psexec -WorkingDirectory "C:\Temp\MyPackage"
        Starts SYSTEM PowerShell session in specific directory for package testing.
 
    .EXAMPLE
        Start-Psexec -ComputerName "Server01" -Credential $cred
        Starts session on remote server with credentials.
 
    .EXAMPLE
        Start-Psexec -Remove
        Removes all PsExec installations from the system.
 
    .NOTES
        Author: AutomateSilent
        Version: 1.0.0
        Compatible: PowerShell 5.1 and later
        Purpose: Simplifies PsExec usage for System Administrators
        Requires: Administrative privileges for SYSTEM context operations
    #>


    [CmdletBinding(DefaultParameterSetName = 'Local')]
    param(
        [Parameter(Position = 0, ParameterSetName = 'Local')]
        [Parameter(Position = 0, ParameterSetName = 'Remote')]
        [Parameter(Position = 0, ParameterSetName = 'UserContext')]
        [ValidateSet('powershell', 'cmd')]
        [string]$Command = 'powershell',

        [Parameter(Position = 1, ParameterSetName = 'Local')]
        [Parameter(Position = 1, ParameterSetName = 'Remote')]
        [Parameter(Position = 1, ParameterSetName = 'UserContext')]
        [ValidateSet('System', 'Interactive', 'UserContext', 'NetworkService')]
        [string]$SessionType = 'System',

        [Parameter(ParameterSetName = 'Remote')]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,

        [Parameter(ParameterSetName = 'UserContext', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Remote')]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(ParameterSetName = 'Local')]
        [Parameter(ParameterSetName = 'Remote')]
        [Parameter(ParameterSetName = 'UserContext')]
        [ValidateNotNullOrEmpty()]
        [string]$WorkingDirectory,

        [Parameter(ParameterSetName = 'Local')]
        [Parameter(ParameterSetName = 'Remote')]
        [Parameter(ParameterSetName = 'UserContext')]
        [switch]$Force,

        [Parameter(ParameterSetName = 'Cleanup', Mandatory = $true)]
        [switch]$Remove
    )

    begin {
        function Write-DeploymentLog {
            <#
            .SYNOPSIS
                Writes formatted log entries for deployment operations with timestamp and severity.
 
            .DESCRIPTION
                Provides standardized logging functionality for deployment scripts with consistent
                formatting, timestamp inclusion, and severity level indication. Supports both
                file logging and console output with appropriate color-coding based on message severity.
 
            .PARAMETER Message
                The message text to be logged.
 
            .PARAMETER Level
                The severity level of the log message. Valid values are:
                - Info: Standard informational messages (default)
                - Warning: Warning messages that require attention
                - Error: Error messages indicating failures
 
            .PARAMETER NoConsole
                If specified, suppresses output to the console. Messages will only be written to the log file.
 
            .EXAMPLE
                Write-DeploymentLog "Starting application installation"
                Logs an informational message with timestamp.
 
            .EXAMPLE
                Write-DeploymentLog "Configuration file not found" -Level Warning
                Logs a warning message with yellow console output.
            #>


            [CmdletBinding()]
            param(
                [Parameter(Mandatory = $true)]
                [string]$Message,

                [ValidateSet('Info', 'Warning', 'Error')]
                [string]$Level = 'Info',

                [switch]$NoConsole
            )

            $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            $logMessage = "[$timestamp] [$Level] $Message"

            if (-not $NoConsole) {
                switch ($Level) {
                    'Info' { Write-Host $logMessage -ForegroundColor Cyan }
                    'Warning' { Write-Warning $logMessage }
                    'Error' { Write-Error $logMessage }
                }
            }
        }

        function Find-PsExecExecutable {
            [CmdletBinding()]
            param(
                [switch]$Force,
                [switch]$Remove
            )

            $searchPaths = @(
                (Get-Command "psexec.exe" -ErrorAction SilentlyContinue).Source,
                "$env:SystemRoot\System32\psexec.exe",
                "$env:SystemRoot\psexec.exe",
                "${env:ProgramFiles}\PSTools\psexec.exe",
                "${env:ProgramFiles(x86)}\PSTools\psexec.exe",
                "$env:TEMP\PSTools\psexec.exe",
                "C:\Tools\psexec.exe"
            )

            if ($Remove) {
                Write-DeploymentLog "Starting PsExec cleanup process" -Level Info
                $removedCount = 0
                
                foreach ($path in $searchPaths) {
                    if ($path -and (Test-Path -Path $path -PathType Leaf)) {
                        try {
                            Write-DeploymentLog "Removing PsExec installation : $path" -Level Info
                            Remove-Item -Path $path -Force -ErrorAction Stop
                            $removedCount++
                        }
                        catch {
                            Write-DeploymentLog "Failed to remove $path : $($_.Exception.Message)" -Level Warning
                        }
                    }
                }

                if ($removedCount -gt 0) {
                    Write-DeploymentLog "PsExec cleanup completed : $removedCount installation(s) removed" -Level Info
                }
                else {
                    Write-DeploymentLog "No PsExec installations found to remove" -Level Info
                }

                return $removedCount
            }

            if ($Force) {
                Write-DeploymentLog "Force parameter specified - bypassing existing PsExec search" -Level Info
                return $null
            }

            foreach ($path in $searchPaths) {
                if ($path -and (Test-Path -Path $path -PathType Leaf)) {
                    Write-DeploymentLog "Located existing PsExec installation : $path" -Level Info
                    return $path
                }
            }

            Write-Verbose "No existing PsExec installation found in standard locations"
            return $null
        }

        function Install-PsExecTool {
            [CmdletBinding()]
            param()

            try {
                Write-DeploymentLog "PsExec not found - initiating automatic download and installation" -Level Info

                # Define paths and URLs
                $downloadUrl = "https://download.sysinternals.com/files/PSTools.zip"
                $tempDir = "$env:TEMP\PSTools_Setup_$((Get-Date).Ticks)"
                $zipFile = "$tempDir\PSTools.zip"
                $extractDir = "$tempDir\Extract"
                $installDir = "$env:SystemRoot\System32"

                # Create temporary directory
                New-Item -Path $tempDir -ItemType Directory -Force | Out-Null
                Write-DeploymentLog "Created temporary staging directory : $tempDir" -Level Info

                # Download PSTools.zip from Microsoft Sysinternals
                Write-DeploymentLog "Downloading PSTools archive from Microsoft Sysinternals" -Level Info
                $webClient = New-Object System.Net.WebClient
                $webClient.DownloadFile($downloadUrl, $zipFile)

                if (-not (Test-Path $zipFile)) {
                    throw "Download operation failed - PSTools.zip file not created"
                }

                $fileSize = (Get-Item $zipFile).Length / 1MB
                Write-DeploymentLog "Download completed successfully : $('{0:N2}' -f $fileSize) MB" -Level Info

                # Extract archive contents
                Write-DeploymentLog "Extracting PSTools archive contents" -Level Info
                Add-Type -AssemblyName System.IO.Compression.FileSystem
                [System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $extractDir)

                # Locate and validate PsExec executable
                $psexecFiles = Get-ChildItem -Path $extractDir -Name "psexec*.exe" -Recurse
                if (-not $psexecFiles) {
                    throw "PsExec executable not found in downloaded archive contents"
                }

                # Select appropriate architecture version
                $psexecSource = $psexecFiles | Select-Object -First 1
                $sourcePath = Join-Path $extractDir $psexecSource
                $destinationPath = Join-Path $installDir "psexec.exe"

                Write-DeploymentLog "Installing PsExec to system directory : $installDir" -Level Info
                Copy-Item -Path $sourcePath -Destination $destinationPath -Force

                # Verify successful installation
                if (Test-Path $destinationPath) {
                    Write-DeploymentLog "PsExec installation completed successfully : $destinationPath" -Level Info
                }
                else {
                    throw "Installation verification failed - PsExec not found in target directory"
                }

                # Cleanup temporary files and directories
                Write-DeploymentLog "Removing temporary files and cleaning up staging area" -Level Info
                Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue

                return $destinationPath

            }
            catch {
                Write-DeploymentLog "PsExec installation failed : $($_.Exception.Message)" -Level Error
                
                # Cleanup temporary files on failure
                if (Test-Path $tempDir) {
                    Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
                }
                
                throw
            }
        }

        function Get-PsExecExecutablePath {
            [CmdletBinding()]
            param(
                [switch]$Force
            )

            # Attempt to locate existing installation
            $existingPath = Find-PsExecExecutable -Force:$Force
            if ($existingPath) {
                return $existingPath
            }

            # Install PsExec if not found
            Write-DeploymentLog "PsExec executable not available - initiating automatic installation" -Level Info
            return Install-PsExecTool
        }

        function Start-PsExecProcess {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string]$PsExecPath,
                
                [Parameter(Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string[]]$Arguments
            )

            try {
                $argumentString = $Arguments -join ' '
                Write-DeploymentLog "Launching PsExec process with arguments : $argumentString" -Level Info

                $startInfo = New-Object System.Diagnostics.ProcessStartInfo
                $startInfo.FileName = $PsExecPath
                $startInfo.Arguments = $argumentString
                $startInfo.UseShellExecute = $true
                $startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal

                $process = [System.Diagnostics.Process]::Start($startInfo)

                if ($process) {
                    # Allow brief startup time for process initialization
                    Start-Sleep -Milliseconds 750

                    if (-not $process.HasExited) {
                        Write-DeploymentLog "PsExec process started successfully : Process ID $($process.Id)" -Level Info
                        return $process
                    }
                    else {
                        $exitCode = $process.ExitCode
                        throw "PsExec process terminated unexpectedly with exit code : $exitCode"
                    }
                }
                else {
                    throw "Process creation failed - unable to start PsExec executable"
                }
            }
            catch {
                Write-DeploymentLog "PsExec process startup failed : $($_.Exception.Message)" -Level Error
                throw
            }
        }

        function Build-PsExecArgumentList {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory = $true)]
                [ValidateSet('powershell', 'cmd')]
                [string]$Command,

                [Parameter(Mandatory = $true)]
                [ValidateSet('System', 'Interactive', 'UserContext', 'NetworkService')]
                [string]$SessionType,

                [string]$ComputerName,

                [System.Management.Automation.PSCredential]$Credential,

                [Parameter(Mandatory = $true)]
                [ValidateNotNullOrEmpty()]
                [string]$WorkingDirectory
            )

            $arguments = @()

            # Accept EULA automatically to prevent interactive prompts
            $arguments += "-accepteula"

            # Computer specification for remote operations
            if ($ComputerName -and ($ComputerName -ne $env:COMPUTERNAME)) {
                $arguments += "\\$ComputerName"
                Write-DeploymentLog "Target system : $ComputerName (Remote)" -Level Info
            }
            else {
                Write-DeploymentLog "Target system : LOCAL" -Level Info
            }

            # Credential handling for authentication
            if ($Credential) {
                $arguments += "-u"
                $arguments += $Credential.UserName
                $arguments += "-p"
                $arguments += $Credential.GetNetworkCredential().Password
                Write-DeploymentLog "Authentication : $($Credential.UserName)" -Level Info
            }

            # Session-specific parameter configuration
            switch ($SessionType) {
                'System' {
                    $arguments += "-i", "-s"
                    Write-DeploymentLog "Session context : NT AUTHORITY\SYSTEM" -Level Info
                }
                'Interactive' {
                    $arguments += "-i"
                    Write-DeploymentLog "Session context : Current User Interactive" -Level Info
                }
                'UserContext' {
                    $arguments += "-i"
                    Write-DeploymentLog "Session context : Specified User ($($Credential.UserName))" -Level Info
                }
                'NetworkService' {
                    $arguments += "-i", "-u", "NetworkService"
                    Write-DeploymentLog "Session context : NT AUTHORITY\NetworkService" -Level Info
                }
            }

            # Working directory configuration
            $arguments += "-w"
            $arguments += "`"$WorkingDirectory`""
            Write-DeploymentLog "Working directory : $WorkingDirectory" -Level Info

            # Command shell specification
            switch ($Command) {
                'powershell' {
                    $arguments += "powershell.exe"
                    Write-DeploymentLog "Shell executable : Windows PowerShell" -Level Info
                }
                'cmd' {
                    $arguments += "cmd.exe"
                    Write-DeploymentLog "Shell executable : Command Prompt" -Level Info
                }
            }

            return $arguments
        }
    }

    process {
        try {
            # Handle cleanup operation if Remove parameter is specified
            if ($PSCmdlet.ParameterSetName -eq 'Cleanup') {
                Write-DeploymentLog "PsExec cleanup operation initiated" -Level Info
                
                $removedCount = Find-PsExecExecutable -Remove
                
                return [PSCustomObject]@{
                    PSTypeName = 'PsExec.CleanupResult'
                    Success = $true
                    RemovedInstallations = $removedCount
                    CompletionTime = Get-Date
                    Message = if ($removedCount -gt 0) { "PsExec cleanup completed : $removedCount installation(s) removed" } else { "No PsExec installations found to remove" }
                }
            }

            # Standard session startup logic
            Write-DeploymentLog "Initializing PsExec session startup sequence" -Level Info

            # Set appropriate default working directory based on session context
            if (-not $WorkingDirectory) {
                $WorkingDirectory = if ($SessionType -eq 'System') { "C:\Windows\Temp" } else { $PWD.Path }
            }

            # Validate session type requirements
            if ($SessionType -eq 'UserContext' -and -not $Credential) {
                $errorMessage = "UserContext session type requires Credential parameter to be specified"
                Write-DeploymentLog $errorMessage -Level Error
                throw $errorMessage
            }

            # Validate working directory accessibility
            if (-not (Test-Path -Path $WorkingDirectory -PathType Container)) {
                $errorMessage = "Specified working directory does not exist or is not accessible : $WorkingDirectory"
                Write-DeploymentLog $errorMessage -Level Error
                throw $errorMessage
            }

            # Obtain PsExec executable path
            $psexecPath = Get-PsExecExecutablePath -Force:$Force

            # Build command line arguments
            $arguments = Build-PsExecArgumentList -Command $Command -SessionType $SessionType -ComputerName $ComputerName -Credential $Credential -WorkingDirectory $WorkingDirectory

            # Launch PsExec process
            $process = Start-PsExecProcess -PsExecPath $psexecPath -Arguments $arguments

            # Create session information object
            $sessionInfo = [PSCustomObject]@{
                PSTypeName = 'PsExec.SessionInfo'
                ProcessId = $process.Id
                Command = $Command.ToUpper()
                SessionType = $SessionType
                ComputerName = if ($ComputerName) { $ComputerName } else { "LOCAL" }
                WorkingDirectory = $WorkingDirectory
                StartTime = Get-Date
                PsExecPath = $psexecPath
                UserContext = if ($Credential) { $Credential.UserName } else { "Default" }
            }

            Write-DeploymentLog "Session initialization completed - interactive shell ready in new window" -Level Info
            Write-Verbose "Session details : $($sessionInfo | Out-String)"
            
            return $sessionInfo

        }
        catch {
            $errorMessage = "PsExec session startup failed : $($_.Exception.Message)"
            Write-DeploymentLog $errorMessage -Level Error
            Write-Error $errorMessage -ErrorAction Stop
        }
    }

    end {
        Write-Verbose "Start-Psexec function execution completed"
    }
}