#-------------------------------------------- # Declare Global Variables and Functions here #-------------------------------------------- #Sample function that provides the location of the script function Get-ScriptDirectory { <# .SYNOPSIS Get-ScriptDirectory returns the proper location of the script. .OUTPUTS System.String .NOTES Returns the correct path within a packaged executable. #> [OutputType([string])] param () if ($hostinvocation -ne $null) { Split-Path $hostinvocation.MyCommand.path } else { Split-Path $script:MyInvocation.MyCommand.Path } } #Sample variable that provides the location of the script [string]$ScriptDirectory = Get-ScriptDirectory function Add-JobTracker { <# .SYNOPSIS Add a new job to the JobTracker and starts the timer. .DESCRIPTION Add a new job to the JobTracker and starts the timer. .PARAMETER Name The name to assign to the Job .PARAMETER JobScript The script block that the Job will be performing. Important: Do not access form controls from this script block. .PARAMETER ArgumentList The arguments to pass to the job .PARAMETER CompleteScript The script block that will be called when the job is complete. The job is passed as an argument. The Job argument is null when the job fails. .PARAMETER UpdateScript The script block that will be called each time the timer ticks. The job is passed as an argument. Use this to get the Job's progress. .EXAMPLE Job-Begin -Name "JobName" ` -JobScript { Param($Argument1)#Pass any arguments using the ArgumentList parameter #Important: Do not access form controls from this script block. Get-WmiObject Win32_Process -Namespace "root\CIMV2" }` -CompletedScript { Param($Job) $results = Receive-Job -Job $Job }` -UpdateScript { Param($Job) #$results = Receive-Job -Job $Job -Keep } .LINK #> Param ( [ValidateNotNull()] [Parameter(Mandatory = $true)] [string]$Name, [ValidateNotNull()] [Parameter(Mandatory = $true)] [ScriptBlock]$JobScript, $ArgumentList = $null, [ScriptBlock]$CompletedScript, [ScriptBlock]$UpdateScript) #Start the Job $job = Start-Job -Name $Name -ScriptBlock $JobScript -ArgumentList $ArgumentList -InitializationScript { Import-Module AWSPowerShell } if ($job -ne $null) { #Create a Custom Object to keep track of the Job & Script Blocks $members = @{ 'Job' = $Job; 'CompleteScript' = $CompletedScript; 'UpdateScript' = $UpdateScript 'Remote' = 'No' 'Session' = 'N/A' } $psObject = New-Object System.Management.Automation.PSObject -Property $members [void]$global:JobTrackerList.Add($psObject) #Start the Timer if (-not $timerJobTracker.Enabled) { $timerJobTracker.Start() } } elseif ($CompletedScript -ne $null) { #Failed Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null } } function Add-RemoteJobTracker { <# .SYNOPSIS Add a new job to the JobTracker and starts the timer. .DESCRIPTION Add a new job to the JobTracker and starts the timer. .PARAMETER Name The name to assign to the Job .PARAMETER JobScript The script block that the Job will be performing. Important: Do not access form controls from this script block. .PARAMETER ArgumentList The arguments to pass to the job .PARAMETER CompleteScript The script block that will be called when the job is complete. The job is passed as an argument. The Job argument is null when the job fails. .PARAMETER UpdateScript The script block that will be called each time the timer ticks. The job is passed as an argument. Use this to get the Job's progress. .EXAMPLE Job-Begin -Name "JobName" ` -JobScript { Param($Argument1)#Pass any arguments using the ArgumentList parameter #Important: Do not access form controls from this script block. Get-WmiObject Win32_Process -Namespace "root\CIMV2" }` -CompletedScript { Param($Job) $results = Receive-Job -Job $Job }` -UpdateScript { Param($Job) #$results = Receive-Job -Job $Job -Keep } .LINK #> Param ( [ValidateNotNull()] [Parameter(Mandatory = $true)] [string]$Name, [ValidateNotNull()] [Parameter(Mandatory = $true)] [ScriptBlock]$JobScript, $ArgumentList = $null, [ScriptBlock]$CompletedScript, [ScriptBlock]$UpdateScript) #Start the Job $varname = $Name+"_session" New-Variable -Name $varname -Scope global Set-Variable -Name $varname -Value (New-PSSession -ComputerName $global:target_ip -Credential $global:creds) -Scope global $session_id = (((Get-Variable -Name $varname -Scope global).value).id) $job = Invoke-Command -AsJob -JobName $Name -ScriptBlock $JobScript -ArgumentList $ArgumentList -Session ((Get-Variable -Name $varname -Scope global).value) Remove-Variable $varname -Scope global if ($job -ne $null) { #Create a Custom Object to keep track of the Job & Script Blocks $members = @{ 'Job' = $Job; 'CompleteScript' = $CompletedScript; 'UpdateScript' = $UpdateScript 'Remote' = 'Yes' 'Session' = $session_id } $psObject = New-Object System.Management.Automation.PSObject -Property $members [void]$global:JobTrackerList.Add($psObject) #Start the Timer if (-not $timerJobTracker.Enabled) { $timerJobTracker.Start() } } } function Update-JobTracker { <# .SYNOPSIS Checks the status of each job on the list. #> #Poll the jobs for status updates $timerJobTracker.Stop() #Freeze the Timer for ($index = 0; $index -lt $global:JobTrackerList.Count; $index++) { $psObject = $global:JobTrackerList[$index] if ($psObject -ne $null) { if ($psObject.Job -ne $null) { if ($psObject.Job.State -eq 'Blocked') { #Try to unblock the job Receive-Job $psObject.Job | Out-Null } elseif ($psObject.Job.State -ne 'Running') { #Call the Complete Script Block if ($psObject.CompleteScript -ne $null) { #$results = Receive-Job -Job $psObject.Job Invoke-Command -ScriptBlock $psObject.CompleteScript -ArgumentList $psObject.Job } if ($psObject.Remote -eq 'Yes') { $id = $psObject.Session Remove-PSSession -Id $id } $global:JobTrackerList.RemoveAt($index) Remove-Job -Job $psObject.Job $index-- #Step back so we don't skip a job } elseif ($psObject.UpdateScript -ne $null) { #Call the Update Script Block Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job } } } else { $global:JobTrackerList.RemoveAt($index) $index-- #Step back so we don't skip a job } } if ($global:JobTrackerList.Count -gt 0) { $timerJobTracker.Start() #Resume the timer } } function Stop-JobTracker { <# .SYNOPSIS Stops and removes all Jobs from the list. #> #Stop the timer $timerJobTracker.Stop() #Remove all the jobs while ($global:JobTrackerList.Count -gt 0) { $job = $global:JobTrackerList[0].Job $global:JobTrackerList.RemoveAt(0) Stop-Job $job Remove-Job $job } } function GlobVar { $regex = "^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$" $global:creds = "" $global:eni_id = "" $global:ingress_groupid = "" $global:ipaddr = "" $global:RootPath = "" $global:startDTM = "" $global:target_ip = "" $global:ws_groups = @() $global:ws_results_index = @() $global:ws_table = @() $global:ws_user = "" $global:instance_table = @() $global:task = "" [string]$region = Get-Content "$env:userprofile\region.txt" Initialize-AWSDefaults -Region $region $global:ipaddr = (Get-NetIPAddress | select ipaddress | where { $_.ipaddress -match $regex -and $_.ipaddress -notlike "169.254*" -and $_.ipaddress -ne "127.0.0.1" })[0].ipaddress Get-EC2Instance | ForEach-Object { $global:instance_table += $_.instances } $global:instance_obj = $global:instance_table | where { $_.privateipaddress -eq $global:ipaddr } $global:vpc_id = $global:instance_obj.vpcid $global:subnets = @() } function Gather-Workspaces { Add-JobTracker -Name "GetWorkspaces" -JobScript ` { $global:vpc_id = $args[0] $global:subnets = $args[1] Get-EC2Subnet | where { $_.vpcid -eq $global:vpc_id } | ForEach-Object { $global:subnets += [string]$_.subnetId } $global:ws_table = @() $global:ws_results_index = @() $workspaces = Get-WKSWorkspaces | where { $global:subnets -contains $_.subnetid } | Sort-Object UserName $directories = Get-DSDirectory $ws_index = 0 foreach ($i in $workspaces) { $ws_index++ $global:ws_results_index += $ws_index $ws_id = $i.workspaceid $ws_user = $i.username $ws_ip = $i.ipaddress $ws_dir = $i.directoryid $ws_dir = (Get-DSDirectory | where { $_.DirectoryId -eq $ws_dir }).name $ws_state = $i.state $data = new-object psobject -property ` @{ "ID" = $ws_index "WorkSpace" = $ws_id "User" = $ws_user "IP-address" = $ws_ip "Directory" = $ws_dir "Status" = $ws_state } $global:ws_table += $data } $global:ws_table | select Workspace, User, "IP-Address", Directory, Status } -ArgumentList $global:vpc_id, $global:subnets -CompletedScript ` { Param ($Job) $global:ws_table = Receive-Job $job $global:grid_results = New-Object System.Collections.ArrayList $grid_results.AddRange($global:ws_table) $datagrid1.DataSource = $grid_results $select_button_cancel.Enabled = $true $select_button_ok.Enabled = $true $datagrid1.Enabled = $true } } function CreateSG { $sg = ($global:instance_obj.securitygroups[0]).groupid $stamp = get-date -Format MMddyyyyhhmmss $groupname = "RescueIngress-$stamp" Add-JobTracker -Name "CreateSG" -JobScript ` { $groupname = $args[0] $global:vpc_id = $args[1] $sg = $args[2] $global:ingress_groupid = New-EC2SecurityGroup -GroupName $groupname -Description $groupname -VpcId $global:vpc_id $sg_obj = New-Object amazon.EC2.Model.UserIdGroupPair $sg_obj.groupid = $sg $ip1 = New-Object amazon.EC2.Model.IpPermission $ip1.IpProtocol = "tcp" $ip1.FromPort = 1 $ip1.ToPort = 65535 $ip1.UserIdGroupPair = $sg_obj Grant-EC2SecurityGroupIngress -GroupId $global:ingress_groupid -IpPermission $ip1 $global:ingress_groupid } -ArgumentList $groupname, $global:vpc_id, $sg -CompletedScript ` { Param ($Job) $global:ingress_groupid = Receive-Job -Name "CreateSG" } Wait-Job -Name "CreateSG" } function AttachSG { # Attaches the ingress security group to the targeted workspace $global:ws_groups += $global:ingress_groupid Add-JobTracker -Name "AttachSG" -JobScript ` { $global:eni_id = $args[0] $global:ws_groups = $args[1] Edit-EC2NetworkInterfaceAttribute -NetworkInterfaceId $global:eni_id -Group $global:ws_groups } -ArgumentList $global:eni_id, $global:ws_groups Wait-Job -Name "AttachSG" -ErrorAction SilentlyContinue } function DetachSG { # Detaches the ingress security group from the targeted workspace $global:ws_groups = $global:ws_groups -notmatch $global:ingress_groupid Add-JobTracker -Name "DetachSG" -JobScript ` { $global:eni_id = $args[0] $global:ws_groups = $args[1] Edit-EC2NetworkInterfaceAttribute -NetworkInterfaceId $global:eni_id -Group $global:ws_groups } -ArgumentList $global:eni_id, $global:ws_groups Wait-Job -Name "DetachSG" -ErrorAction SilentlyContinue } function RemoveSG { # Removes the ingress security group, clears global variable Add-JobTracker -Name "RemoveSG" -JobScript ` { $sg = Get-EC2SecurityGroup | where { $_.groupname -like "*RescueIngress*" } if ($sg) { foreach ($i in $sg) { $sg_name = $i.GroupName $sg_id = $i.GroupId $attached_eni_list = @() $attached_eni_list += Get-EC2NetworkInterface | where { ($_.groups | Select-Object -ExpandProperty Groupname) -contains $sg_name } if ($attached_eni_list.count -gt 0) { foreach ($i in $attached_eni_list) { $temp_eni = $i.NetworkInterfaceId $temp_groups = (Get-EC2NetworkInterfaceAttribute -NetworkInterfaceId $temp_eni -Group "").groups | Select-Object -ExpandProperty groupid $temp_groups = $temp_groups -notmatch $sg_id Edit-EC2NetworkInterfaceAttribute -NetworkInterfaceId $temp_eni -Group $temp_groups -ErrorAction SilentlyContinue | Out-Null } } Remove-EC2SecurityGroup -GroupId $sg_id -Confirm:$false -ErrorAction silentlycontinue -Force | out-null } } } -CompletedScript ` { Param ($Job) $sg = Receive-Job $job $global:ingress_groupid = "" } Wait-Job -Name "RemoveSG" -ErrorAction SilentlyContinue } function CheckSG { # Checks to see if the global variable ingress_group is populated # indicating the existence of the group; if it does not, the # security group is created and attached to the targeted Workspace. if (!$global:ingress_groupid) { CreateSG } Add-JobTracker -Name "GetGroups" -JobScript ` { $global:eni_id = $args[0] (Get-EC2NetworkInterfaceAttribute -NetworkInterfaceId $global:eni_id -Group "").groups | Select-Object -ExpandProperty groupid } -ArgumentList $global:eni_id -CompletedScript ` { Param ($Job) $global:ws_groups = @() $group_obj = Receive-Job $job $global:ws_groups += $group_obj if ($global:ws_groups -notcontains $global:ingress_groupid) { AttachSG } } } function DiagRun { #Parent function to import task functions and outline tasks for the log # gathering procedure. $formWorkspacesDiagnostic.refresh() $main_label_task.text = "Gather Workspace Log Data" $main_label_status.text = "Initiating LogRun..." # List of performed LogRun tasks (functions) LogRun-Setup # LogRun-Gather-Logs # LogRun-Gather-Registry # LogRun-Gather-EventLogs # LogRun-Gather-GPResult # LogRun-Gather-Applications # LogRun-Gather-Services # LogRun-Gather-StreamingCore # LogRun-Gather-VolumeInfo # LogRun-Gather-ProfileInfo # LogRun-Gather-IPConfig # LogRun-Gather-MaxCallDepth # if ($global:rescue -eq $true) # { # Rescue # } # LogRun-Zip-Logs LogRun-Finish # if ($global:rescue_reboot -eq $true) # { # Rescue-Reboot # } } function LogRun-Setup { # Checks for presence of security group, creates/attaches if necessary. # A PS session is opened for the LogRun task, the root working path is # set and the start time is stored. A folder is created at the root # working path. Write-Debug 'Logrun-Setup init' $main_label_status.text = "Creating security group..." Write-Debug 'CheckSG' CheckSG $main_label_status.text = "Opening session..." $global:jobList = @() $global:startDTM = (Get-Date) $global:RootPath = "c:\WorkSpacesLogs\" $main_label_status.text = "Creating working directory..." Write-Debug 'adding rootpath job' Add-RemoteJobTracker -Name "CreateRootPath" -JobScript ` { $global:RootPath = $args[0] Remove-Item $global:RootPath -recurse -Force -ErrorAction SilentlyContinue | out-null New-Item $global:RootPath -type directory -force | out-null } -ArgumentList $global:RootPath -CompletedScript ` { Write-Debug "RootpathCreated" } Wait-Job -Name "CreateRootPath" Write-Debug "LogRun-Setup Complete" $main_label_status.text = "Gathering Workspace Logs..." } function LogRun-Gather-Logs { # Workspace logs are copied into child folder of root working path $LogPath = $global:rootpath + "WSLogs\" $jobname = "GatherLogs" Write-Debug "Submitting job $jobname" Add-RemoteJobTracker -Name $jobname -JobScript ` { $LogPath = $args[0] New-Item $LogPath -type directory -force | out-null $log_items = @() $log_items += "C:\ProgramData\Teradici" $log_items += "C:\Program Files\Amazon\Ec2ConfigService\Logs" $log_items += "C:\Program Files\Amazon\Ec2ConfigService\Settings" $log_items += "C:\Program Files\Amazon\Ec2ConfigService\Scripts\UserScript.ps1" $log_items += "C:\Program Files\Amazon\Skylight\Logs" $log_items += "C:\Program Files\Amazon\Skylight\.applications" $log_items += "C:\Program Files\Amazon\WorkSpacesConfig" $log_items += "C:\Program Files (x86)\Teradici\PCoIP Agent\pcoip_agent_release_manifest.txt" $log_items += "C:\Program Files (x86)\Teradici\PCoIP Agent\pcoip_printing_manifest.txt" foreach ($i in $log_items) { $target = $logpath + ($i.Substring(3)) $item = Get-Item $i -ErrorAction silentlycontinue if ($item) #does item exist? { if ($item.versioninfo) #item is file { $targetdir = $target.substring(0, (($target.length) - ($target.split('\')[-1].length))) xcopy /Y $i $targetdir | Out-Null } else #item is directory { Copy-Item $i $target -recurse -force } } } } -ArgumentList $LogPath $global:jobList += $jobname } function LogRun-Gather-Registry { # Workspaces-relevant registry hives are exported to child folder of # root working path $RegPath = $global:rootpath + "RegistryKeys\" $jobname = "GatherRegistry" Write-Debug "Submitting job $jobname" Add-RemoteJobTracker -Name $jobname -JobScript ` { $RegPath = $args[0] $keys = "HKLM\SOFTWARE\Amazon", "HKLM\SOFTWARE\Teradici", "HKLM\SOFTWARE\Wow6432Node\Teradici" New-Item $RegPath -type directory -force | out-null foreach ($i in $keys) { $filename = $RegPath + ($i.Replace('\', '-')) + ".hiv" REG SAVE $i $filename -y | Out-Null } } -ArgumentList $RegPath $global:jobList += $jobname } function LogRun-Gather-EventLogs { # Exports system, application and setup logs for the last 30 days to # child folder of root working path $EventPath = $global:rootpath + "EventLogs\" $jobname = "GatherEventLogs" Add-RemoteJobTracker -Name $jobname -JobScript ` { $EventPath = $args[0] $eventlogs = "Application", "Setup", "System" new-item $EventPath -type directory -Force | out-null foreach ($i in $eventlogs) { $filename = $EventPath + "$i.evtx" wevtutil epl $i $filename /q:"*[System[TimeCreated[timediff(@SystemTime) <= 2592000000]]]" # integer is 30 days in ms } } -ArgumentList $EventPath $global:jobList += $jobname } function LogRun-Gather-GPResult { # Exports html report of gpresult to child folder of root working path $GPRPath = $global:rootpath + "GPResult\" $jobname = "GatherGPResult" Add-RemoteJobTracker -Name $jobname -JobScript ` { $GPRPath = $args[0] $global:ws_user = $args[1] new-item $GPRPath -type directory -Force | out-null $filename = $GPRPath + $global:ws_user + "-gpresult.html" gpresult /h $filename /user $global:ws_user } -ArgumentList $GPRPath,$global:ws_user $global:jobList += $jobname } function LogRun-Gather-Applications { # Exports list of installed applications (32 and 64 bit) to child # folder of root working path $AppPath = $global:rootpath + "InstalledApps\" $jobname = "GatherApplications" Add-RemoteJobTracker -Name $jobname -JobScript ` { $AppPath = $args[0] new-item $AppPath -type directory -Force | out-null $app_keys = @() $app_keys += "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" $app_keys += "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" foreach ($i in $app_keys) { $applications = @(); Get-ItemProperty $i | Select DisplayName, DisplayVersion, Publisher, InstallDate, HelpLink, UninstallString, PSChildName, PSParentPath | ForEach-Object ` { if ($_.displayname) { $app_name = $_.displayname } else { $app_name = $_.pschildname } if ($_.psparentpath -like "*Wow6432Node*") { $app_arch = "64-bit" } else { $app_arch = "32-bit" } $app_version = $_.DisplayVersion $app_publisher = $_.publisher $app_installdate = $_.installdate $app_helplink = $_.helplink $app_uninstallstring = $_.uninstallstring $app_obj = New-Object psobject -Property ` @{ Name = $app_name Version = $app_version Publisher = $app_publisher InstallDate = $app_installdate HelpLink = $app_helplink UninstallString = $app_uninstallstring Architecture = $app_arch } $applications += $app_obj } } $filename = $AppPath + "Applications.csv" $applications | Select Name, Version, Publisher, InstallDate, HelpLink, UninstallString, Architecture | Export-Csv -NoTypeInformation $filename } -ArgumentList $AppPath $global:jobList += $jobname } function LogRun-Gather-Services { # Exports list of services running with domain credentials to child # folder of root working path $ServicePath = $global:rootpath + "Services\" $jobname = "GatherServices" Add-RemoteJobTracker -Name $jobname -JobScript ` { $ServicePath = $args[0] new-item $ServicePath -type directory -Force | out-null $services = Get-WmiObject -Class "win32_service" | where { $_.startname -notlike "*localsystem*" -and $_.startname -notlike "NT AUTHORITY\*" } | select name, displayname, startname, startmode; if ($services.count -gt 0) { $filename = $ServicePath + "Services.csv" $services | Export-Csv -NoTypeInformation $filename } else { $filename = $ServicePath + "NoServices.txt" "No services have been detected that are running with non-local permissions." > $filename } } -ArgumentList $ServicePath $global:jobList += $jobname } function LogRun-Gather-StreamingCore { # Checks for "C:\ProgramData\Amazon\StreamingCore\Log". If present, the # contents are exported to a child folder of the root working path. $StreamingPath = $global:rootpath + "StreamingCore\" $jobname = "GatherStreamingCore" Add-RemoteJobTracker -Name $jobname -JobScript ` { $StreamingPath = $args[0] new-item $StreamingPath -type directory -Force | out-null $StreamingCore = "C:\ProgramData\Amazon\StreamingCore\Log\" $StreamingCoreExists = test-path $StreamingCore if ($StreamingCoreExists) { Copy-Item $StreamingCore $StreamingPath -recurse } else { $filename = $StreamingPath + "NoStreamingCore.txt" "$StreamingCore was not found. WAM may not be installed." > $filename } } -ArgumentList $StreamingPath $global:jobList += $jobname } function LogRun-Gather-VolumeInfo { # Collects information about all attached volumes, their size and free # space. This is formatted into a table, exported to a .csv file and # stored in a child folder of the root working path. $VolumePath = $global:rootpath + "VolumeInfo\" $jobname = "GatherVolumeInfo" Add-RemoteJobTracker -Name $jobname -JobScript ` { $VolumePath = $args[0] new-item $VolumePath -type directory -Force | out-null $filename = $VolumePath + "Volumes.csv" function Get-VolumeInfo { Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -ne 5 } |` Sort-Object -Property Name | Select-Object Name, VolumeName, ` @{ "Label" = "DiskSize(GB)"; "Expression" = { "{0:N}" -f ($_.Size/1GB) -as [float] } }, ` @{ "Label" = "FreeSpace(GB)"; "Expression" = { "{0:N}" -f ($_.FreeSpace/1GB) -as [float] } }, ` @{ "Label" = "%Free"; "Expression" = { "{0:N}" -f ($_.FreeSpace/$_.Size * 100) -as [float] } } } Get-VolumeInfo | Export-Csv -NoTypeInformation $filename } -ArgumentList $VolumePath $global:jobList += $jobname } function LogRun-Gather-ProfileInfo { # Collects and formats information related to user profiles present # on the workspace into a csv file which is then stored in a child # folder of the root working path $ProfileSizePath = $global:rootpath + "UserProfileSize\" $jobname = "GatherProfileInfo" Add-RemoteJobTracker -Name $jobname -JobScript ` { $ProfileSizePath = $args[0] $filename = $ProfileSizePath + "UserProfileSize.csv" New-Item $ProfileSizePath -type directory -Force | out-null remove-item $filename -Force -erroraction silentlycontinue $profile_home_drive = $env:HOMEDRIVE $profile_dir = $profile_home_drive + "\Users\" $profile_users = @() Get-ChildItem $profile_dir | ForEach-Object { $profile_users += $_.Name } $profile_table = @() foreach ($i in $profile_users) { $profile_user_dir = $profile_dir + $i $profile_blob = Get-ChildItem $profile_user_dir -Recurse -Force -ErrorAction SilentlyContinue $profile_size = ($profile_blob | Measure-Object -Property Length -Sum).sum $profile_file_count = $profile_blob | Measure-Object | %{ $_.Count } $profile_size_mb = "{0:N2}" -f ($profile_size/1MB) $profile_size_gb = "{0:N2}" -f ($profile_size/1GB) $data = New-Object PSobject -Property ` @{ Username = $i ProfileSize = $profile_size ProfileSizeMB = $profile_size_mb ProfileSizeGB = $profile_size_gb FileCount = $profile_file_count } $profile_table += $data } $profile_table | Select-Object Username, ProfileSize, ProfileSizeMB, ProfileSizeGB, FileCount | Export-Csv -NoTypeInformation $filename } -ArgumentList $ProfileSizePath $global:jobList += $jobname } function LogRun-Gather-IPConfig { # Dumps the output of ipconfig into a text file, which is then stored # in a child folder of the root working path. $NetworkPath = $global:rootpath + "IPConfig\" $jobname = "GatherIPConfig" Add-RemoteJobTracker -Name $jobname -JobScript ` { $NetworkPath = $args[0] New-Item $NetworkPath -type directory -force | out-null $filename = $NetworkPath + "IPConfig-all.txt" ipconfig /all > $filename } -ArgumentList $NetworkPath $global:jobList += $jobname } function Logrun-Gather-MaxCallDepth { # Calculates MaxCallDepth and stores the information in a text file # located in a child folder of the root working path. $MaxDepthPath = $global:rootpath + "MaxCallDepth\" $jobname = "GatherMaxCallDepth" Add-RemoteJobTracker -Name $jobname -JobScript ` { $MaxDepthPath = $args[0] $filename = $MaxDepthPath + "MaxCallDepth.txt" Function GetCallDepth() { param ( [string]$rootRegistryKey, [int]$currentCallDepth ) $maxDetails = ($currentCallDepth, $rootRegistryKey, $true) ForEach ($subKey in Get-ChildItem -literalPath $rootRegistryKey -Force -ErrorAction SilentlyContinue) { Try { $callDetails = GetCallDepth $subKey.PSPath ($currentCallDepth + 1) if ($maxDetails[0] -lt $callDetails[0]) { $maxDetails = $callDetails } if ($callDetails[2] -eq $false) { clear-host; break } } Catch [exception] { Write-Host ("failed for {0} - Line {1}. {2}" -f $subKey.PSPath, $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message) $maxDetails = ($maxDetails[0], $maxDetails[1], $false) clear-host; break } } return $maxDetails } function GetCallDepthCurrentUser() { param ( [string]$rootRegistryKey ) $maxCallDepth = GetCallDepth $rootRegistryKey 1 "Max Call Depth: {0}" -f $maxCallDepth[0] "Key: {0}" -f $maxCallDepth[1] } New-Item $MaxDepthPath -type directory -force | out-null $ErrorActionPreference = "SilentlyContinue" try { $rootRegistryKey = "HKCU:" GetCallDepthCurrentUser $rootRegistryKey > $filename } Catch [exception] { "failed - Line {0}. {1}" -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message } } -ArgumentList $MaxDepthPath $global:jobList += $jobname } function LogRun-Zip-Logs { # Zips the contents of the root working path and places it at d:\. This # file is then copied back to the desktop of the machine running the # application. Wait-Job $global:jobList $main_label_status.text = "Compressing logs and beaming back to mothership..." $source = $global:rootpath $datetime = Get-Date -format yyyyMMddHHmmss $zip_root = "\\$global:target_ip\c$" $global:zip_filename = "$global:ws_id-$datetime.zip" $zip_unc_path = $zip_root + $global:zip_filename $zip_output = "c:\$global:zip_filename" $zip_path_local = "x:\$global:zip_filename" $main_savezip.InitialDirectory = "$env:USERPROFILE\Desktop" $main_savezip.Filename = $global:zip_filename $main_savezip.DefaultExt = ".zip" $main_savezip.Filter = "Zip Archives (.zip)|*.zip" $main_savezip.showdialog() $global:copy_dest = $main_savezip.Filename $jobname = "ZipUp" Add-RemoteJobTracker -Name $jobname -JobScript ` { $source = $args[0] $zip_output = $args[1] Add-Type -Assembly 'System.IO.Compression.FileSystem' [System.IO.Compression.ZipFile]::CreateFromDirectory($source, $zip_output, 'Optimal', $false) } -ArgumentList $source,$zip_output Wait-Job $jobname $jobname = "ZipCopy" Add-JobTracker -Name $jobname -JobScript ` { $zip_root = $args[0] $global:creds = $args[1] $zip_path_local = $args[2] $global:copy_dest = $args[3] New-PSDrive -Name X -PSProvider filesystem -Root $zip_root -Credential $global:creds -Persist Copy-Item $zip_path_local $global:copy_dest Remove-PSDrive -Name X } -ArgumentList $zip_root,$global:creds,$zip_path_local,$global:copy_dest Wait-Job $jobname $jobname = "ZipCleanup" Add-RemoteJobTracker -Name $jobname -JobScript ` { $global:RootPath = $args[0] $zip_output = $args[1] Remove-Item $global:RootPath -Force -recurse Remove-Item $zip_output -force } -ArgumentList $global:RootPath,$zip_output Wait-Job $jobname } function LogRun-Finish { # Calculates elapsed time, instructs user on where to find the zip file. # The PS session is closed, and the security group is detached from the # Workspace and removed. $global:endDTM = (Get-Date) $elapsedtime = ($global:endDTM - $global:startDTM).totalseconds $elapsedtime = [math]::Round($elapsedtime, 2) Remove-PSSession $global:session DetachSG RemoveSG $main_label_status.text = "Complete!" $main_label_elapsed.text = "$elapsedtime seconds" $main_label_elapsed.visible = $true $main_label_elapsed_static.visible = $true } function Rescue { # Rescue my Workspace! script by randelld@, refactored and rewritten to be # function-based $main_label_task.text = "Rescue My Workspace!" $main_label_status.text = "Initiating rescue..." Rescue-FirewallStatus Rescue-ServiceCheck Rescue-ExeCheck Rescue-MetaDataCheck Rescue-PortCheck Rescue-PCOIPFlag Rescue-Finish } function Rescue-FirewallStatus { # Checks Windows Firewall status on remote Workspace, storing the status # for each profile into a table. $main_label_status.text = "Checking Firewall Status..." $profiles = "Domain", "Public", "Standard" $global:fw_status_table = @() $fw_key_base = "Registry::HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\" foreach ($i in $profiles) { $key = $fw_key_base + $i + "Profile" $status = Invoke-Command -Session $global:session { (Get-ItemProperty -Path $args[0]).EnableFirewall } -args $key if ($status -eq 0) { $enabled = "DISABLED" } else { $enabled = "ENABLED" $global:fwcount++ } $data = New-Object System.Management.Automation.PSObject -Property ` @{ Profile = $i Enabled = $enabled } $global:fw_status_table += $data } if ($global:fwcount -gt 0) { Rescue-FirewallRuleCheck } else { $global:fw_rules = "N/A, Windows Firewall is Disabled" } } function Rescue-FirewallRuleCheck { # If the Firewall was enabled for any profile during # Rescue-FirewallStatus, we check for the WorkSpace-related firewall # rules and their current status. If any of the rules are enabled, # the user is warned of this and instructed to correct. $main_label_status.text = "Firewall enabled! Checking WorkSpace rules..." $global:rules_table = @() Invoke-Command -Session $global:session ` { Function Get-FireWallRule { Param ($Name, $Direction, $Enabled, $Protocol, $profile, $action, $grouping) $Rules = (New-object –comObject HNetCfg.FwPolicy2).rules If ($name) { $rules = $rules | where-object { $_.name –like $name } } $rules } } $fwrules = Invoke-Command -Session $global:session { Get-FireWallRule | where { $_.grouping -like "*pcoip*" } } foreach ($i in $fwrules) { $rule_name = $i.name $rule_status = $i.enabled $data = New-Object psobject -Property ` @{ Name = $rule_name Enabled = $rule_status } $global:rules_table += $data } $disabled = $global:rules_table | where { $_.Enabled -ne "True" } $global:disabled_count = $disabled.count } function Rescue-ServiceCheck { # Check for WorkSpace services. If any are not running, we issue a # start-service command in attempts to correct this. $main_label_status.text = "Checking Workspace Services..." $services = "SkyLightWorkspaceConfigService", "PCoIPAgent", "Ec2Config" $global:service_table = @() foreach ($i in $services) { $service_obj = Invoke-Command -Session $global:session ` { $name = $args[0] Get-Service | Where-Object { $_. name -eq $name } } -args $i $status = $service_obj.status $name = $service_obj.name $data = New-Object psobject -Property ` @{ Name = $name Status = $status } $global:service_table += $data } $stopped = $global:service_table | where { $_.status -eq "Stopped" } $global:stopped_count = $stopped.count if ($stopped_count -gt 0) { $main_label_status.text = "$stopped_count services are currently stopped. attempting to start." foreach ($i in $stopped.name) { $main_label_status.text = "Attempting to start $i. If this fails, gather WorkSpace Logs." Invoke-Command -Session $global:session { $name = $args[0]; start-service $name } -args $i } } } function Rescue-ExeCheck { # Looking for running processes owned by Workspace executables $main_label_status.text = "Checking for processes owned by WorkSpace executables..." $global:proc_table = @() $process_names = "Ec2Config", "pcoip_agent", "SkyLightWorkspaceConfigService" foreach ($i in $process_names) { $data = New-Object psobject -Property ` @{ Name = $i Running = "" } $proc = Invoke-Command -Session $global:session { $proc = $args[0]; Get-Process | where { $_.ProcessName -like "*$proc*" } } -args $i if ($proc) { $data.running = "Running" } else { $data.running = "NotRunning" } $global:proc_table += $data } } function Rescue-MetaDataCheck { # Check to see if metadata server is accessible $main_label_status.text = "Checking for EC2Config metadata accessibility..." Invoke-Command -Session $global:session { $request = [System.Net.WebRequest]::Create('http://169.254.169.254') } $response = Invoke-Command -Session $global:session { $request.GetResponse() } if ($response.StatusCode) { $global:metadata_check = $true } else { $global:metadata_check = $false } } function Rescue-PortCheck { # Check to see if WorkSpace ports are listening $main_label_status.text = "Checking to see if WorkSpace is listening on required ports..." $ports = @{ "Remote Desktop Service" = 3389; "PCoIP Agent" = 4172; "Skylight Service" = 8200 } $global:port_table = @() foreach ($i in $ports.GetEnumerator()) { $Name = $i.Name $port = $i.Value $Netstat = Invoke-Command -Session $global:session { $port = $args[0]; netstat -aon | findstr $port } -args $port if ($Netstat -like "*TCP*:$port*LISTENING*") { $listening = "Listening" } else { $listening = "NotListening" } $data = New-Object System.Management.Automation.PSObject -ArgumentList ` @{ Port = $port Listening = $listening } $global:port_table += $data } } function Rescue-PCoIPFlag { # Clears version flags to ensure that new PCoIP agent is installed # on the next restart of the Workspace. $main_label_status.text = "Clearing PCoIP version flags to force re-installation at next reboot..." Invoke-Command -Session $global:session ` { $input_path = ‘C:\Program Files\Amazon\Skylight\.applications’ $regex = ‘\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' $VersionNumbers = select-string -Path $input_path -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value } If ($VersionNumbers) { $num = 0 ForEach ($_ in $VersionNumbers) { # Reinstall PCoIP on next reboot: (Get-Content ‘C:\Program Files\Amazon\Skylight\.applications’) | Foreach-Object { $_ -replace $VersionNumbers[$num], "" } | Set-Content ‘C:\Program Files\Amazon\Skylight\.applications’ write-good "Cleared Version" $VersionNumbers[$num] $num = $num + 1 } } } } function Rescue-Finish { # Indicates completion to user. PS Session is closed, security group # is removed. User is given the opportunity to restart the WorkSpace # from this prompt to force PCoIP agent installation. $main_label_status.text = "Displaying results..." Call-RMWResults_psf $RescuePath = $global:rootpath + "RescueMyWorkspace\" [string]$results = "" $results += "Rescue My Workspace Results`r`n" $results += "Workspace: $global:ws_id`r`n" $results += "`r`n`r`n" $results += $global:fw_status_text $results += $global:fw_rules_text $results += $global:service_text $results += $global:exe_text $results += $global:metadata_text $results += $global:port_text Invoke-Command -Session $global:session ` { $RescuePath = $args[0] $results = $args[1] $filename = $RescuePath + "RMW.log" New-Item $RescuePath -Type directory -Force | Out-Null $results > $filename } -Args $RescuePath, $results } function Rescue-Reboot { # If specified in the previous prompt this will issue a call to restart # the targeted WorkSpace. $main_label_status.text = "Rebooting workspace $global:ws_id..." Restart-WKSWorkspace -Request @{ "WorkspaceId" = $global:ws_id } $wshell = New-Object -ComObject Wscript.Shell $wshell.Popup("Reboot requested for $global:ws_id!", 0, "Done", 0x0) }