├── Assets ├── CWHelper │ ├── SelectedWSMetrics │ │ ├── WorkSpaceHistoricalLatency1.png │ │ ├── WorkSpaceLatency1.png │ │ └── WorkSpacesSessionLaunch1.png │ ├── WorkSpacesCPU-Start.png │ ├── WorkSpacesCPUTemplate.JSON │ ├── WorkSpacesConnectionSummaryTemplate.json │ ├── WorkSpacesDisk-Start.png │ ├── WorkSpacesDiskTemplate.JSON │ ├── WorkSpacesHistoricalLatency-Start.png │ ├── WorkSpacesHistoricalLatencyTemplate.JSON │ ├── WorkSpacesLatencyTemplate.JSON │ ├── WorkSpacesMemory-Start.png │ ├── WorkSpacesMemoryTemplate.JSON │ ├── WorkSpacesSessionLaunch-Start.png │ ├── WorkSpacesSessionLaunchTemplate.JSON │ ├── WorkSpacesUDPPacketLoss-Start.png │ └── WorkSpacesUDPTemplate.JSON ├── EUCToolkit-Helper.psm1 ├── EUCToolkit-MainGUI.xml └── Settings.csv ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── Start-EUCToolkit.ps1 /Assets/CWHelper/SelectedWSMetrics/WorkSpaceHistoricalLatency1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/SelectedWSMetrics/WorkSpaceHistoricalLatency1.png -------------------------------------------------------------------------------- /Assets/CWHelper/SelectedWSMetrics/WorkSpaceLatency1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/SelectedWSMetrics/WorkSpaceLatency1.png -------------------------------------------------------------------------------- /Assets/CWHelper/SelectedWSMetrics/WorkSpacesSessionLaunch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/SelectedWSMetrics/WorkSpacesSessionLaunch1.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesCPU-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesCPU-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesCPUTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "CPUUsage", "WorkspaceId", "ws-123456"] 6 | ], 7 | "region": "us-east-1", 8 | "period": 300, 9 | "title": "CPU 24 hours", 10 | "width": 500, 11 | "height": 200, 12 | "start": "-PT24H", 13 | "end": "P0D" 14 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesConnectionSummaryTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "ConnectionSuccess", "WorkspaceId", "ws-123456"], 6 | [ ".", "ConnectionFailure", ".", "."], 7 | [ ".", "ConnectionAttempt", ".", "."], 8 | [ ".", "SessionLaunchTime", ".", "."], 9 | [ ".", "SessionDisconnect", ".", "."] 10 | ], 11 | "period": 300, 12 | "title": "Connection summary over the past 24 Hours", 13 | "width": 500, 14 | "height": 200, 15 | "start": "-PT24H", 16 | "end": "P0D" 17 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesDisk-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesDisk-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesDiskTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ 6 | "AWS/WorkSpaces", 7 | "RootVolumeDiskUsage", 8 | "WorkspaceId", 9 | "ws-123456" 10 | ], 11 | [ 12 | ".", 13 | "UserVolumeDiskUsage", 14 | ".", 15 | "." 16 | ] 17 | ], 18 | "title": "Disk Usage past 24 hours", 19 | "width": 500, 20 | "height": 200, 21 | "start": "-PT24H", 22 | "end": "P0D", 23 | "period": 300 24 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesHistoricalLatency-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesHistoricalLatency-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesHistoricalLatencyTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "InSessionLatency", "WorkspaceId", "ws-123456"] 6 | ], 7 | "period": 300, 8 | "annotations": { 9 | "horizontal": [ 10 | { 11 | "color": "#2ca02c", 12 | "value": 50, 13 | "fill": "below" 14 | }, 15 | [ 16 | { 17 | "color": "#ffbb78", 18 | "value": 50 19 | }, 20 | { 21 | "value": 100, 22 | "label": "" 23 | } 24 | ], 25 | { 26 | "color": "#d62728", 27 | "value": 100, 28 | "fill": "above" 29 | } 30 | ] 31 | }, 32 | "title": "Historical Latency over the past 24 Hours", 33 | "width": 500, 34 | "height": 200, 35 | "start": "-PT24H", 36 | "end": "P0D" 37 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesLatencyTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "InSessionLatency", "WorkspaceId", "ws-123456" ] 6 | ], 7 | "period": 300, 8 | "annotations": { 9 | "horizontal": [ 10 | { 11 | "color": "#2ca02c", 12 | "value": 50, 13 | "fill": "below" 14 | }, 15 | [ 16 | { 17 | "color": "#ffbb78", 18 | "value": 50 19 | }, 20 | { 21 | "value": 100, 22 | "label": "" 23 | } 24 | ], 25 | { 26 | "color": "#d62728", 27 | "value": 100, 28 | "fill": "above" 29 | } 30 | ] 31 | }, 32 | "title": "Historical Latency over the past 24 Hours", 33 | "width": 500, 34 | "height": 200, 35 | "start": "-PT24H", 36 | "end": "P0D" 37 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesMemory-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesMemory-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesMemoryTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "MemoryUsage", "WorkspaceId", "ws-123456"] 6 | ], 7 | "period": 300, 8 | "title": "Memory usage past 24 Hours", 9 | "width": 500, 10 | "height": 200, 11 | "start": "-PT24H", 12 | "end": "P0D" 13 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesSessionLaunch-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesSessionLaunch-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesSessionLaunchTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "SessionLaunchTime", "WorkspaceId", "ws-123456" ] 6 | ], 7 | "period": 300, 8 | "title": "Session Launch time past month", 9 | "width": 500, 10 | "height": 200, 11 | "start": "-PT1008H", 12 | "end": "P0D" 13 | } -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesUDPPacketLoss-Start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/euc-toolkit/250901f2b368bcac17d7f1ab9136d1aae216b1ea/Assets/CWHelper/WorkSpacesUDPPacketLoss-Start.png -------------------------------------------------------------------------------- /Assets/CWHelper/WorkSpacesUDPTemplate.JSON: -------------------------------------------------------------------------------- 1 | { 2 | "view": "timeSeries", 3 | "stacked": false, 4 | "metrics": [ 5 | [ "AWS/WorkSpaces", "UDPPacketLossRate", "WorkspaceId", "ws-123456"] 6 | ], 7 | "period": 300, 8 | "title": "UDP Packet Loss over the past 24 Hours", 9 | "width": 500, 10 | "height": 200, 11 | "start": "-PT24H", 12 | "end": "P0D" 13 | } -------------------------------------------------------------------------------- /Assets/EUCToolkit-Helper.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | #> 16 | 17 | <# 18 | .SYNOPSIS 19 | This script module provides helper functions for the EUC Toolkit. 20 | .DESCRIPTION 21 | This script module performs the heavy lifting for the EUC toolkit. This includes building the local db object, optimizing API calls, 22 | returning error and success messages, generating dashboard images, and other functionality part of the EUC toolkit. For more information, 23 | see the link below: 24 | https://github.com/aws-samples/euc-toolkit 25 | #> 26 | 27 | # Current WorkSpaces Regions. See link below for current WorkSpaces availability 28 | # https://docs.aws.amazon.com/workspaces/latest/adminguide/azs-workspaces.html 29 | 30 | 31 | function Get-LocalWorkSpacesDB(){ 32 | param( 33 | $DeployedRegions, 34 | $throttleControl 35 | ) 36 | # This function build a PSObject that contains all of your WorkSpaces information. The object will act as a local DB for the GUI. 37 | # If you need to have object persistence to save API calls, this function can be replaced with a function that calls your persistent store. 38 | $WorkSpacesDDB = @() 39 | # Finds all current WorkSpaces. If Active Directory cannot be reached, those attributes are omitted. 40 | foreach($DeployedRegion in $DeployedRegions){ 41 | $wksResponse = Get-WKSWorkSpaces -Region $DeployedRegion.Region -DirectoryId $DeployedRegion.DirectoryId -limit 25 -NoAutoIteration -select * -NextToken $null 42 | $RegionalWks = $wksResponse.Workspaces 43 | $token = $wksResponse.NextToken 44 | while ($null -ne $token) { 45 | $wksResponse = Get-WKSWorkSpaces -Region $DeployedRegion.Region -DirectoryId $DeployedRegion.DirectoryId -limit 25 -NoAutoIteration -select * -NextToken $token 46 | $RegionalWks += $wksResponse.Workspaces 47 | $token = $wksResponse.NextToken 48 | if($throttleControl){ 49 | Start-Sleep -Milliseconds 200 50 | } 51 | } 52 | foreach ($Wks in $RegionalWks){ 53 | $adErr = $false 54 | $wks | Add-Member -NotePropertyName "Region" -NotePropertyValue $DeployedRegion.Region 55 | if($Wks.WorkspaceProperties.Protocols -like "WSP"){$wsProto = 'DCV'}elseif ($Wks.WorkspaceProperties.Protocols -like "PCOIP"){$wsProto = 'PCoIP'} else{$wsProto = 'BYOP'} 56 | $wks | Add-Member -NotePropertyName "Protocol" -NotePropertyValue $wsProto 57 | $wks | Add-Member -NotePropertyName "RegCode" -NotePropertyValue ($DeployedRegion | Where-Object {$_.directoryId -eq $Wks.directoryId}).RegistrationCode 58 | try{ 59 | $ADUser = Get-ADUser -Identity $Wks.UserName -Properties "EmailAddress" 60 | }catch{ 61 | $adErr = $true 62 | } 63 | if($adErr -eq $false){ 64 | $wks | Add-Member -NotePropertyName "FirstName" -NotePropertyValue ($ADUser.GivenName) 65 | $wks | Add-Member -NotePropertyName "LastName" -NotePropertyValue ($ADUser.Surname) 66 | $wks | Add-Member -NotePropertyName "Email" -NotePropertyValue ($ADUser.EmailAddress) 67 | }else{ 68 | $wks | Add-Member -NotePropertyName "FirstName" -NotePropertyValue "AD Info Not Available" 69 | $wks | Add-Member -NotePropertyName "LastName" -NotePropertyValue "AD Info Not Available" 70 | $wks | Add-Member -NotePropertyName "Email" -NotePropertyValue "AD Info Not Available" 71 | } 72 | $WorkSpacesDDB += $wks 73 | } 74 | } 75 | return $WorkSpacesDDB 76 | } 77 | 78 | function Get-WksServiceQuotasDB(){ 79 | param( 80 | $DeployedRegions 81 | ) 82 | $WSServiceQuota = @() 83 | $UniqueRegions = $DeployedRegions | Select-Object Region -Unique 84 | foreach($WksRegion in $UniqueRegions){ 85 | $region = $WksRegion.Region 86 | $DeployedRegionsTemp = New-Object -TypeName PSobject 87 | $DeployedRegionsTemp | Add-Member -NotePropertyName "Region" -NotePropertyValue $region 88 | # For more information on WorkSpaces Quotas, see https://docs.aws.amazon.com/workspaces/latest/adminguide/workspaces-limits.html. 89 | #Total Regional WorkSpaces 90 | try{ 91 | $TotalQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-34278094" -Region $region).Value 92 | }catch{ 93 | $TotalQuota = "N/A" 94 | } 95 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaWks" -NotePropertyValue $TotalQuota 96 | #Total Regional General Purpose 4XL WorkSpaces 97 | try{ 98 | $Gp4xlQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-465DA8AF" -Region $region).Value 99 | }catch{ 100 | $Gp4xlQuota = "N/A" 101 | } 102 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaWks4xl" -NotePropertyValue $Gp4xlQuota 103 | #Total Regional General Purpose 8XL WorkSpaces 104 | try{ 105 | $Gp8xlQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-C266A5F4" -Region $region).Value 106 | }catch{ 107 | $Gp8xlQuota = "N/A" 108 | } 109 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaWks8xl" -NotePropertyValue $Gp8xlQuota 110 | # StandBy WorkSpaces 111 | try{ 112 | $StandbyQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-9A67B5CB" -Region $region).Value 113 | }catch{ 114 | $StandbyQuota = "N/A" 115 | } 116 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaStandby" -NotePropertyValue $StandbyQuota 117 | # GraphicsPro WorkSpaces 118 | try{ 119 | $GraphicsProQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-254B485B" -Region $region).Value 120 | }catch{ 121 | $GraphicsProQuota = "N/A" 122 | } 123 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaGraphicsPro" -NotePropertyValue $GraphicsProQuota 124 | # Graphics.g4dn WorkSpaces 125 | try{ 126 | $GraphicsG4Quota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-BCACAEBC" -Region $region).Value 127 | }catch{ 128 | $GraphicsG4Quota = "N/A" 129 | } 130 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaG4dn" -NotePropertyValue $GraphicsG4Quota 131 | # Graphics.g4dn WorkSpaces Pro 132 | try{ 133 | $GraphicsG4ProQuota = (Get-SQServiceQuota -ServiceCode workspaces -QuotaCode "L-BE9A8466" -Region $region).Value 134 | }catch{ 135 | $GraphicsG4ProQuota = "N/A" 136 | } 137 | $DeployedRegionsTemp | Add-Member -NotePropertyName "quotaG4dnPro" -NotePropertyValue $GraphicsG4ProQuota 138 | 139 | $WSServiceQuota += $DeployedRegionsTemp 140 | } 141 | return $WSServiceQuota 142 | } 143 | 144 | function Get-WksDirectories(){ 145 | param( 146 | $throttleControl 147 | ) 148 | $regions = @('us-east-1','us-west-2', 'ap-south-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ca-central-1', 'eu-central-1','eu-west-1', 'eu-west-2', 'sa-east-1') 149 | $DeployedDirectories = @() 150 | $personalDirectory = New-Object -TypeName Amazon.WorkSpaces.Model.DescribeWorkspaceDirectoriesFilter 151 | $personalDirectory.Name = "WORKSPACE_TYPE" 152 | $personalDirectory.Values += "PERSONAL" 153 | # Find regions that have WorkSpaces deployments 154 | foreach($region in $regions){ 155 | $directoryResponse = Get-WKSWorkspaceDirectories -Region $region -limit 25 -Filter $personalDirectory -NoAutoIteration -select * -NextToken $null 156 | $RegionDirectories = $directoryResponse.Directories 157 | $token = $directoryResponse.NextToken 158 | while ($null -ne $token) { 159 | $directoryResponse = Get-WKSWorkspaceDirectories -Region $region -limit 25 -Filter $personalDirectory -NoAutoIteration -select * -NextToken $token 160 | $RegionDirectories += $directoryResponse.Directories 161 | $token = $directoryResponse.NextToken 162 | if($throttleControl){ 163 | Start-Sleep -Milliseconds 200 164 | } 165 | } 166 | if($RegionDirectories){ 167 | foreach($WksDirectory in $RegionDirectories){ 168 | $WksDirectory | Add-Member -NotePropertyName "Region" -NotePropertyValue $region 169 | $subnetA = Get-EC2Subnet -SubnetId $WksDirectory.SubnetIds[0] -Region $region 170 | $subnetB = Get-EC2Subnet -SubnetId $WksDirectory.SubnetIds[1] -Region $region 171 | $dirAvailableIPs = $subnetA.AvailableIpAddressCount + $subnetB.AvailableIpAddressCount 172 | $WksDirectory | Add-Member -NotePropertyName "DirectoryAvailableIPs" -NotePropertyValue $dirAvailableIPs 173 | $DeployedDirectories += $WksDirectory 174 | } 175 | }else{ 176 | Write-Host "Skipping $region" 177 | } 178 | } 179 | return $DeployedDirectories 180 | } 181 | 182 | function Get-AllBundles(){ 183 | param( 184 | $Regions, 185 | $Custom, 186 | $throttleControl 187 | ) 188 | $Bundles = @() 189 | foreach($Region in $Regions.Region){ 190 | if($custom -eq $true){ 191 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -NoAutoIteration -select * -NextToken $null 192 | $Bundles += $bundleResponse.Bundles 193 | $token = $bundleResponse.NextToken 194 | while ($null -ne $token) { 195 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -NoAutoIteration -select * -NextToken $token 196 | $Bundles += $bundleResponse.Bundles 197 | $token = $bundleResponse.NextToken 198 | if($throttleControl){ 199 | Start-Sleep -Milliseconds 200 200 | } 201 | } 202 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -Owner 'AMAZON' -NoAutoIteration -select * -NextToken $null 203 | $Bundles += $bundleResponse.Bundles 204 | $token = $bundleResponse.NextToken 205 | while ($null -ne $token) { 206 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -Owner 'AMAZON' -NoAutoIteration -select * -NextToken $token 207 | $Bundles += $bundleResponse.Bundles 208 | $token = $bundleResponse.NextToken 209 | if($throttleControl){ 210 | Start-Sleep -Milliseconds 200 211 | } 212 | } 213 | }else{ 214 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -Owner 'AMAZON' -NoAutoIteration -select * -NextToken $null 215 | $Bundles += $bundleResponse.Bundles 216 | $token = $bundleResponse.NextToken 217 | while ($null -ne $token) { 218 | $bundleResponse = Get-WKSWorkspaceBundle -Region $Region -Owner 'AMAZON' -NoAutoIteration -select * -NextToken $token 219 | $Bundles += $bundleResponse.Bundles 220 | $token = $bundleResponse.NextToken 221 | Start-Sleep -Milliseconds 200 222 | } 223 | } 224 | } 225 | return $Bundles 226 | } 227 | 228 | function Update-RunningMode(){ 229 | # This function updates the running mode on a selected WorkSpace. For more information see the link below: 230 | # https://docs.aws.amazon.com/powershell/latest/reference/items/Edit-WKSWorkspaceProperty.html 231 | param( 232 | $UpdateReq 233 | ) 234 | $WorkSpaceId = $UpdateReq.WorkSpaceId 235 | $region = $UpdateReq.Region 236 | 237 | if($UpdateReq.CurrentRunMode -eq "AUTO_STOP"){ 238 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $region -WorkspaceProperties_RunningMode ALWAYS_ON" 239 | } 240 | elseif($UpdateReq.CurrentRunMode -eq "ALWAYS_ON"){ 241 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $region -WorkspaceProperties_RunningMode AUTO_STOP" 242 | } 243 | try{ 244 | $scriptblock = [Scriptblock]::Create($callBlock) 245 | $logging = Invoke-Command -scriptblock $scriptblock 246 | }Catch{ 247 | $msg = $_ 248 | $logging = New-Object -TypeName PSobject 249 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Running Mode Change" 250 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 251 | } 252 | return $logging 253 | } 254 | 255 | function Update-ComputeType(){ 256 | # This function updates the compute type associated with a targeted WorkSpace. For more information see the link below: 257 | # https://docs.aws.amazon.com/powershell/latest/reference/items/Edit-WKSWorkspaceProperty.html 258 | param( 259 | $ComputeReq 260 | ) 261 | $WorkSpaceId = $ComputeReq.WorkSpaceId 262 | $region = $ComputeReq.Region 263 | $TargetCompute = $ComputeReq.TargetCompute 264 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $Region -WorkspaceProperties_ComputeTypeName $TargetCompute" 265 | $scriptblock = [Scriptblock]::Create($callBlock) 266 | try{ 267 | $logging = Invoke-Command -scriptblock $scriptblock 268 | }Catch{ 269 | $msg = $_ 270 | $logging = New-Object -TypeName PSobject 271 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Running Mode Change" 272 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 273 | } 274 | return $logging 275 | } 276 | 277 | function Invoke-RemoteAssist{ 278 | # This function invokes the remote assist functionality. Refer to the blog and README for more info. 279 | param( 280 | [String]$privateIP 281 | ) 282 | $parameters="/offerRA " +$privateIP 283 | $exe="msra.exe" 284 | start-process $exe $parameters -Wait 285 | } 286 | 287 | function Update-RootVolume{ 288 | # This function increases the available capacity on the WorkSpaces' Root volume. For more information see the links below: 289 | # https://docs.aws.amazon.com/workspaces/latest/adminguide/modify-workspaces.html#change_volume_sizes 290 | # https://docs.aws.amazon.com/powershell/latest/reference/items/Edit-WKSWorkspaceProperty.html 291 | param( 292 | $WorkSpaceReq 293 | ) 294 | $CurrentUser = $WorkSpaceReq.CurrentUserStorage 295 | $CurrentRoot = $WorkSpaceReq.CurrentRootStorage 296 | $WorkSpaceId = $WorkSpaceReq.WorkSpaceId 297 | $Region = $WorkSpaceReq.Region 298 | if($CurrentUser -lt 100 -and $CurrentRoot -lt 175){ 299 | $msg = "Unable to extend $WorkSpaceId's Root volume until you have increased the User volume to 100GB (Currently User is $CurrentUser GB)." 300 | #Write-Logger -message $msg 301 | $logging = New-Object -TypeName PSobject 302 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Root Increase" 303 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 304 | }elseif($CurrentUser -ge 100){ 305 | $msg = "Enter new Root Volume size in GB. Note, it must be larger or equal to 175 GB." 306 | 307 | [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') 308 | $title = 'Root Volume Increase' 309 | $requestedSize = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title) 310 | $requestedSize = $requestedSize -as [int] 311 | if ( -not ([string]::IsNullOrEmpty( $requestedSize ))){ 312 | if ($requestedSize -ge 175 -and $requestedSize -gt $CurrentRoot){ 313 | try{ 314 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $region -WorkspaceProperties_RootVolumeSizeGib $requestedSize" 315 | $scriptblock = [Scriptblock]::Create($callBlock) 316 | $logging += Invoke-Command -scriptblock $scriptblock 317 | }Catch{ 318 | $msg = $_ 319 | $logging = New-Object -TypeName PSobject 320 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During User Increase" 321 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 322 | } 323 | 324 | }else{ 325 | $msg = "$WorkSpaceId was unable to extend its Root Volume to $requestedSize GB since its not greater than or equal to 175." 326 | $logging = New-Object -TypeName PSobject 327 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Root Increase" 328 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 329 | } 330 | } 331 | }else{ 332 | if ( -not ([string]::IsNullOrEmpty( $requestedSize ))){ 333 | $msg = "$WorkSpaceId was unable to extend its Root Volume to $requestedSize GB since your User Volume is not equal to 100GB." 334 | $logging = New-Object -TypeName PSobject 335 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Root Increase" 336 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 337 | } 338 | } 339 | return $logging 340 | } 341 | 342 | function Set-WKSProtocol{ 343 | param( 344 | $ProtocolModifyReq 345 | ) 346 | $logging = @() 347 | 348 | foreach($req in $ProtocolModifyReq){ 349 | $WorkSpaceId = $req.WorkSpaceId 350 | $Region = $req.Region 351 | if($req.Protocol -eq 'PCOIP'){ 352 | $TargetProtocol = 'WSP' 353 | } 354 | elseif($req.Protocol -eq 'WSP'){ 355 | $TargetProtocol = 'PCOIP' 356 | } 357 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $Region -WorkspaceProperties_Protocols $TargetProtocol" 358 | $scriptblock = [Scriptblock]::Create($callBlock) 359 | try{ 360 | $logging += Invoke-Command -scriptblock $scriptblock 361 | }Catch{ 362 | $msg = $_ 363 | $tmplogging = New-Object -TypeName PSobject 364 | $tmplogging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error Terminating WorkSpaces" 365 | $tmplogging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 366 | $logging += $tmplogging 367 | } 368 | } 369 | 370 | return $logging 371 | } 372 | 373 | function Update-UserVolume{ 374 | # This function increases the available capacity on the WorkSpaces' User volume. For more information see the links below: 375 | # https://docs.aws.amazon.com/workspaces/latest/adminguide/modify-workspaces.html#change_volume_sizes 376 | # https://docs.aws.amazon.com/powershell/latest/reference/items/Edit-WKSWorkspaceProperty.html 377 | param( 378 | $WorkSpaceReq 379 | ) 380 | $CurrentUser = $WorkSpaceReq.CurrentUserStorage 381 | $CurrentRoot = $WorkSpaceReq.CurrentRootStorage 382 | $WorkSpaceId = $WorkSpaceReq.WorkSpaceId 383 | $Region = $WorkSpaceReq.Region 384 | if($CurrentRoot -lt 175){ 385 | $msg = "Enter new User Volume size in GB. Note, it must be larger than $CurrentUser GB and, to go above 100GB, the Root volume will need to be atleast 175GB." 386 | } 387 | elseif($CurrentRoot -ge 175){ 388 | $msg = "Enter new User Volume size in GB. Note, it must be larger than $CurrentUser GB." 389 | } 390 | [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') 391 | $title = 'User Volume Increase' 392 | $requestedSize = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title) 393 | $requestedSize = $requestedSize -as [int] 394 | 395 | if ($requestedSize -le $CurrentUser){ 396 | $msg = "$WorkSpaceId was unable to extend its User Volume to $requested GB since it currently is $CurrentUser GB." 397 | $logging = New-Object -TypeName PSobject 398 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During User Increase" 399 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 400 | } 401 | elseif($requestedSize -gt 100 -and $CurrentRoot -lt 175){ 402 | $msg = "$WorkSpaceId was unable to extend its User Volume to $requested GB since Root is not at least 175GB (Currently $CurrentRoot GB)." 403 | $logging = New-Object -TypeName PSobject 404 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During User Increase" 405 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 406 | } 407 | else{ 408 | $callBlock = "Edit-WKSWorkspaceProperty -WorkspaceId $WorkSpaceId -Region $Region -WorkspaceProperties_UserVolumeSizeGib $requestedSize" 409 | $scriptblock = [Scriptblock]::Create($callBlock) 410 | try{ 411 | $logging = Invoke-Command -scriptblock $scriptblock 412 | }Catch{ 413 | $msg = $_ 414 | $logging = New-Object -TypeName PSobject 415 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During User Increase" 416 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 417 | } 418 | } 419 | 420 | return $logging 421 | } 422 | 423 | function Optimize-APIRequest{ 424 | # This function optimizes several API calls so that the GUI can pass in a large list of targets, and APIs 425 | # can be optimized to use as few calls as possible. Some APIs can be built out to call 25 targets at a time 426 | # while some cannot. See the link below for more information: 427 | # https://docs.aws.amazon.com/powershell/latest/reference/items/WorkSpaces_cmdlets.html 428 | [CmdletBinding()] 429 | param( 430 | $requestInfo, 431 | $APICall 432 | ) 433 | $callBlock = "" 434 | $counter = 0 435 | $logging = @() 436 | $builder = @() 437 | 438 | if($APICall.split("-")[0] -like "Start" -or $APICall.split("-")[0] -like "Stop" -or $APICall.split("-")[0] -like "Restart"){ 439 | foreach($call in $requestInfo){ 440 | $counter++ 441 | if($counter -eq $requestInfo.WorkSpaceId.count){ 442 | $builder += $call.WorkSpaceId 443 | $region = $call.Region 444 | $callBlock = "$APICall -Region $region -WorkSpaceId $builder" 445 | $scriptblock = [Scriptblock]::Create($callBlock) 446 | $logging += Invoke-Command -scriptblock $scriptblock 447 | } 448 | elseif($counter % 25 -ne 0){ 449 | $builder += $call.WorkSpaceId + "," 450 | }else{ 451 | $builder += $call.WorkSpaceId 452 | $region = $call.Region 453 | $callBlock = "$APICall -Region $region -WorkSpaceId $builder" 454 | $scriptblock = [Scriptblock]::Create($callBlock) 455 | $logging += Invoke-Command -scriptblock $scriptblock 456 | $builder = @() 457 | } 458 | } 459 | } 460 | elseif($APICall.split("-")[0] -like "Remove"){ 461 | foreach($call in $requestInfo.TermList){ 462 | $counter++ 463 | if($counter -eq $requestInfo.TermList.count){ 464 | $builder += $call.WorkSpaceId 465 | $region = $call.Region 466 | $callBlock = "$APICall -Region $region -WorkSpaceId $builder -force" 467 | $scriptblock = [Scriptblock]::Create($callBlock) 468 | try{ 469 | $logging += Invoke-Command -scriptblock $scriptblock 470 | }Catch{ 471 | $msg = $_ 472 | $tmplogging = New-Object -TypeName PSobject 473 | $tmplogging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error Terminating WorkSpaces" 474 | $tmplogging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 475 | $logging += $tmplogging 476 | } 477 | } 478 | elseif($counter % 25 -ne 0){ 479 | $builder += $call.WorkSpaceId + "," 480 | }else{ 481 | $builder += $call.WorkSpaceId 482 | $region = $call.Region 483 | $callBlock = "$APICall -Region $region -WorkSpaceId $builder -force" 484 | $scriptblock = [Scriptblock]::Create($callBlock) 485 | try{ 486 | $logging += Invoke-Command -scriptblock $scriptblock 487 | }Catch{ 488 | $msg = $_ 489 | $tmplogging = New-Object -TypeName PSobject 490 | $tmplogging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error Terminating WorkSpaces" 491 | $tmplogging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 492 | $logging += $tmplogging 493 | } 494 | $builder = @() 495 | } 496 | } 497 | } 498 | elseif($APICall.split("-")[0] -like "Reset" -or $APICall.split("-")[0] -like "Restore"){ 499 | foreach($call in $requestInfo){ 500 | $builder = $call.WorkSpaceId 501 | $region = $call.Region 502 | $callBlock = "$APICall -Region $region -WorkSpaceId $builder" 503 | $scriptblock = [Scriptblock]::Create($callBlock) 504 | try{ 505 | $logging += Invoke-Command -scriptblock $scriptblock 506 | }Catch{ 507 | $msg = $_ 508 | $logging = New-Object -TypeName PSobject 509 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During WorkSpaces $APICall" 510 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 511 | } 512 | } 513 | } 514 | elseif($APICall.split("-")[0] -like "Enable"){ 515 | foreach($call in $requestInfo){ 516 | $callBlock = "Edit-WKSWorkspaceState -WorkspaceId " + $call.WorkSpaceId + " -WorkspaceState ADMIN_MAINTENANCE -Region " + $call.Region 517 | $scriptblock = [Scriptblock]::Create($callBlock) 518 | try{ 519 | $logging += Invoke-Command -scriptblock $scriptblock 520 | }Catch{ 521 | $msg = $_ 522 | $logging = New-Object -TypeName PSobject 523 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error Enabling Admin Maintenance" 524 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 525 | } 526 | } 527 | } 528 | elseif($APICall.split("-")[0] -like "Disable"){ 529 | foreach($call in $requestInfo){ 530 | $callBlock = "Edit-WKSWorkspaceState -WorkspaceId " + $call.WorkSpaceId + " -WorkspaceState AVAILABLE -Region " + $call.Region 531 | $scriptblock = [Scriptblock]::Create($callBlock) 532 | try{ 533 | $logging += Invoke-Command -scriptblock $scriptblock 534 | }Catch{ 535 | $msg = $_ 536 | $logging = New-Object -TypeName PSobject 537 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error Disabling Admin Maintenance" 538 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 539 | } 540 | } 541 | } 542 | return $logging 543 | } 544 | 545 | function Initialize-MigrateWorkSpace(){ 546 | # This function will migrate a WorkSpace to a new bundle. See the links below for more information: 547 | # https://docs.aws.amazon.com/workspaces/latest/adminguide/migrate-workspaces.html 548 | # https://docs.aws.amazon.com/powershell/latest/reference/items/Start-WKSWorkspaceMigration.html 549 | [CmdletBinding()] 550 | param( 551 | $requestInfo 552 | ) 553 | 554 | $logging = $null 555 | $callBlock = "Start-WKSWorkspaceMigration -SourceWorkspaceId " + $requestInfo.WorkSpaceId + " -BundleId " + $requestInfo.BundleId + " -Region " + $requestInfo.Region 556 | $scriptblock = [Scriptblock]::Create($callBlock) 557 | try{ 558 | $logging = Invoke-Command -scriptblock $scriptblock 559 | }Catch{ 560 | $msg = $_ 561 | $logging = New-Object -TypeName PSobject 562 | $logging | Add-Member -NotePropertyName "ErrorCode" -NotePropertyValue "Error During Migrate" 563 | $logging | Add-Member -NotePropertyName "ErrorMessage" -NotePropertyValue $msg 564 | } 565 | 566 | return $logging 567 | } 568 | 569 | function Show-MessageError(){ 570 | # This function provides visual error messages for the GUI. 571 | param( 572 | [String]$message, 573 | [String]$title 574 | ) 575 | [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') 576 | [System.Windows.MessageBox]::Show($message,$title,'OK','Error') 577 | } 578 | function Show-MessageSuccess(){ 579 | # This function provides visual success messages for the GUI. 580 | param( 581 | [String]$message, 582 | [String]$title 583 | ) 584 | [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') 585 | [System.Windows.MessageBox]::Show($message,$title,'OK') 586 | } 587 | 588 | function Get-CloudWatchImagesServiceMetrics(){ 589 | # This function will generate dashboard images for your metrics portal. It targets the deafult metrics for WorkSpaces. 590 | param( 591 | [String]$workingDirectory, 592 | [String]$WSId, 593 | [String]$AWSProfile, 594 | [String]$region, 595 | [String]$CWRun 596 | ) 597 | $path=$workingDirectory 598 | 599 | #Latency Average last 24 Hours 600 | $json = Get-Content ($path+"WorkSpacesHistoricalLatencyTemplate.json") -Raw 601 | $jsonobj = $json | ConvertFrom-Json 602 | $data = @("AWS/WorkSpaces","InSessionLatency","WorkspaceId",$WSId) 603 | $jsonobj.metrics[0]=$data 604 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 605 | set-content -Path ($path+"WorkSpacesHistoricalLatency.json") -Value $jsonobj 606 | $JsonFile = Get-Content -Raw -Path ($path+"WorkSpacesHistoricalLatency.json") 607 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 608 | [byte[]]$bytes = $image.ToArray() 609 | Set-Content -Path ($path+"SelectedWSMetrics\WorkSpacesHistoricalLatency"+$CWRun+".png") -Value $bytes -Encoding Byte 610 | 611 | #Now UDP Packet Loss 612 | $json = Get-Content ($path+"\WorkSpacesUDPTemplate.json") -Raw 613 | $jsonobj = $json | ConvertFrom-Json 614 | $data = @("AWS/WorkSpaces","WorkSpacesUDPPacketLossRate","WorkspaceId",$WSId) 615 | $jsonobj.metrics[0]=$data 616 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 617 | set-content -Path ($path+"\WorkSpacesUDP.json") -Value $jsonobj 618 | $JsonFile = Get-Content -Raw -Path ($path+"\WorkSpacesUDP.json") 619 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 620 | [byte[]]$bytes = $image.ToArray() 621 | Set-Content -Path ($path+"SelectedWSMetrics\WorkSpacesUDP"+$CWRun+".png") -Value $bytes -Encoding Byte 622 | 623 | #Connection Summary 624 | $json = Get-Content ($path+"\WorkSpacesConnectionSummaryTemplate.json") -Raw 625 | $jsonobj = $json | ConvertFrom-Json 626 | $data = @("AWS/WorkSpaces","ConnectionSuccess","WorkspaceId",$WSId) 627 | $jsonobj.metrics[0]=$data 628 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 629 | set-content -Path ($path+"\WorkSpacesConnectionSummary.json") -Value $jsonobj 630 | $JsonFile = Get-Content -Raw -Path ($path+"\WorkSpacesConnectionSummary.json") 631 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 632 | [byte[]]$bytes = $image.ToArray() 633 | Set-Content -Path ($path+"SelectedWSMetrics\WorkSpacesConnectionSummary"+$CWRun+".png") -Value $bytes -Encoding Byte 634 | 635 | $global:CloudWatchImageProcessComplete=$true 636 | } 637 | 638 | function Get-CloudWatchImagesWorkSpaceMetrics(){ 639 | # This function will generate dashboard images for your metrics portal. It targets the additional metrics for WorkSpaces through CloudWatch Agent. 640 | param( 641 | [String]$workingDirectory, 642 | [String]$WSId, 643 | [String]$AWSProfile, 644 | [String]$region, 645 | [String]$CWRun 646 | ) 647 | 648 | $path=$workingDirectory 649 | #Process to get the CPU PNG File 650 | $json = Get-Content ($path+"\WorkSpacesCPUTemplate.json") -Raw 651 | $jsonobj = $json | ConvertFrom-Json 652 | $data = @("AWS/WorkSpaces","CPUUsage","WorkspaceId",$WSId) 653 | $jsonobj.metrics[0]=$data 654 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 655 | set-content -Path ($path+"\WorkSpacesCPU.json") -Value $jsonobj 656 | $JsonFile = Get-Content -Raw -Path ($path+"\WorkSpacesCPU.json") 657 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 658 | [byte[]]$bytes = $image.ToArray() 659 | Set-Content -Path ($path+"\SelectedWSMetrics\WorkSpacesCPU"+$CWRun+".png") -Value $bytes -Encoding Byte 660 | 661 | #Disk 662 | $json = Get-Content ($path+"\WorkSpacesDiskTemplate.json") -Raw 663 | $jsonobj = $json | ConvertFrom-Json 664 | $data = @("AWS/WorkSpaces","RootVolumeDiskUsage","WorkspaceId",$WSId) 665 | $jsonobj.metrics[0]=$data 666 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 667 | set-content -Path ($path+"\WorkSpacesDisk.json") -Value $jsonobj 668 | $JsonFile = Get-Content -Raw -Path ($path+"\WorkSpacesDisk.json") 669 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 670 | [byte[]]$bytes = $image.ToArray() 671 | Set-Content -Path ($path+"\SelectedWSMetrics\WorkSpacesDisk"+$CWRun+".png") -Value $bytes -Encoding Byte 672 | 673 | #Memory 674 | $json = Get-Content ($path+"\WorkSpacesMemoryTemplate.json") -Raw 675 | $jsonobj = $json | ConvertFrom-Json 676 | $data = @("AWS/WorkSpaces","MemoryUsage","WorkspaceId",$WSId) 677 | $jsonobj.metrics[0]=$data 678 | $jsonobj = $jsonobj | ConvertTo-Json -depth 6 679 | set-content -Path ($path+"\WorkSpacesMemory.json") -Value $jsonobj 680 | $JsonFile = Get-Content -Raw -Path ($path+"\WorkSpacesMemory.json") 681 | $image= get-CWMetricWidgetImage -MetricWidget $JsonFile -Region $region 682 | [byte[]]$bytes = $image.ToArray() 683 | Set-Content -Path ($path+"\SelectedWSMetrics\WorkSpacesMemory"+$CWRun+".png") -Value $bytes -Encoding Byte 684 | 685 | $global:CloudWatchImageProcessCompleteWSMetrics=$true 686 | } 687 | 688 | function Get-CloudWatchStats(){ 689 | # This function will read your access metrics from CloudTrail and then return object for the GUI to present. 690 | param( 691 | [String]$workingDirectory, 692 | [String]$WorkSpaceAccessLogs, 693 | [String]$CloudTrailLogs, 694 | [String]$WorkSpaceId, 695 | [String]$AccessLogsRegion, 696 | [String]$CloudTrailRegion, 697 | [String]$CWRun 698 | ) 699 | 700 | #Query Logins for the User 701 | $Startdate = (New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-Date)).TotalSeconds 702 | $Startdate = $Startdate - 604800 703 | $Enddate = Get-Date 704 | $Enddate = $Enddate.ToUniversalTime() 705 | $Enddate = (New-TimeSpan -Start (Get-Date "01/01/1970") -End ($Enddate)).TotalSeconds 706 | 707 | #Initiate Query if Access Logs are configured 708 | if ($WorkSpaceAccessLogs -ne ""){ 709 | $queryString=('fields @message |filter detail.workspaceId="'+$WorkSpaceId+'"') 710 | $queryResultUserAccess=Start-CWLQuery -QueryString $queryString -LogGroupName $WorkSpaceAccessLogs -StartTime $Startdate -EndTime $Enddate -Region $AccessLogsRegion 711 | $queryResultUserAccessComplete=$false 712 | $queryString=('fields @message |filter `detail.eventName`="ModifyWorkspaceProperties" |filter `detail.requestParameters.workspaceId`="'+$WorkSpaceId+'"') 713 | $queryResultWorkSpaceChanges=Start-CWLQuery -QueryString ('fields @message |filter `detail.eventName`="ModifyWorkspaceProperties" |filter `detail.requestParameters.workspaceId`="'+$WorkSpaceId+'"') -LogGroupName $WorkSpaceAccessLogs -StartTime $Startdate -EndTime $Enddate -Region $AccessLogsRegion 714 | $queryResultCloudTrailComplete = $false 715 | } 716 | else{ 717 | $queryResultUserAccessComplete = $true 718 | $queryResultCloudTrailComplete = $true 719 | 720 | } 721 | 722 | # Wait for the WorkSpace Access query to complete 723 | while ($queryResultUserAccessComplete -eq $false){ 724 | foreach ($completeQuerry in $queryStatus){ 725 | if ($completeQuerry.QueryId -eq $queryResultUserAccess){ 726 | $queryResultUserAccessComplete = $true 727 | # Loop through the records and output to a CSV 728 | $loadResults=Get-CWLQueryResult -QueryId $queryResultUserAccess -Region $AccessLogsRegion 729 | $x=0 730 | $OutputObj = @() 731 | while ($x-lt $loadResults.Statistics.RecordsMatched){ 732 | $userAccessLog = $loadResults.Results[$x][0].Value | ConvertFrom-Json 733 | $NewObj = New-Object -TypeName PSobject 734 | $NewObj | Add-Member -NotePropertyName "Time" -NotePropertyValue $userAccessLog.time 735 | $NewObj | Add-Member -NotePropertyName "ClientVersion" -NotePropertyValue $userAccessLog.detail.clientVersion 736 | $NewObj | Add-Member -NotePropertyName "IP" -NotePropertyValue $userAccessLog.detail.clientIpAddress 737 | $NewObj | Add-Member -NotePropertyName "Platform" -NotePropertyValue $userAccessLog.detail.clientPlatform 738 | $OutputObj += $NewObj 739 | $x=$x+1 740 | } 741 | # Now load the object into a CSV to load into the Form 742 | $OutputObj | Export-Csv -Path ($workingDirectory+"\SelectedWSMetrics\ConnectionHistory"+$CWRun+".csv") -NoTypeInformation 743 | } 744 | } 745 | sleep -Seconds 1 746 | $queryStatus = Get-CWLQuery -Status Complete -Region $AccessLogsRegion 747 | } 748 | 749 | $queryStatus = Get-CWLQuery -Status Complete -Region $AccessLogsRegion 750 | # Wait for the CloudTrail query to complete 751 | $queryString=('fields @message |filter `detail.eventName`="ModifyWorkspaceProperties" |filter `detail.requestParameters.workspaceId`="'+$WorkSpaceId+'"') 752 | $queryResultWorkSpaceChanges=Start-CWLQuery -QueryString ('fields @message |filter `detail.eventName`="ModifyWorkspaceProperties" |filter `detail.requestParameters.workspaceId`="'+$WorkSpaceId+'"') -LogGroupName $WorkSpaceAccessLogs -StartTime $Startdate -EndTime $Enddate -Region $AccessLogsRegion 753 | $queryResultCloudTrailComplete = $false 754 | while ($queryResultCloudTrailComplete -eq $false){ 755 | foreach ($completeQuerry in $queryStatus){ 756 | if ($completeQuerry.QueryId -eq $queryResultWorkSpaceChanges){ 757 | $queryResultCloudTrailComplete = $true 758 | $x=0 759 | $OutputObj = @() 760 | $loadResults=Get-CWLQueryResult -QueryId $queryResultWorkSpaceChanges -Region $AccessLogsRegion 761 | while ($x-lt $loadResults.Statistics.RecordsMatched){ 762 | $CloudTrailLog = $loadResults.Results[$x][0].Value | ConvertFrom-Json 763 | write-host $CloudTrailLog.detail.requestParameters 764 | $NewObj = New-Object -TypeName PSobject 765 | $NewObj | Add-Member -NotePropertyName "Time" -NotePropertyValue $CloudTrailLog.detail.eventTime 766 | $NewObj | Add-Member -NotePropertyName "Region" -NotePropertyValue $CloudTrailLog.detail.awsRegion 767 | $NewObj | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $CloudTrailLog.detail.requestParameters.workspaceId 768 | $NewObj | Add-Member -NotePropertyName "ComputeType" -NotePropertyValue $CloudTrailLog.detail.requestParameters.workspaceProperties.computeTypeName 769 | $NewObj | Add-Member -NotePropertyName "RunningMode" -NotePropertyValue $CloudTrailLog.detail.requestParameters.workspaceProperties.runningMode 770 | $NewObj | Add-Member -NotePropertyName "UserVolume" -NotePropertyValue $CloudTrailLog.detail.requestParameters.workspaceProperties.userVolumeSizeGib 771 | $NewObj | Add-Member -NotePropertyName "RootVolume" -NotePropertyValue $CloudTrailLog.detail.requestParameters.workspaceProperties.rootVolumeSizeGib 772 | $OutputObj += $NewObj 773 | $x=$x+1 774 | } 775 | # Now load the object into a CSV to load into the Form 776 | $OutputObj | Export-Csv -Path ($workingDirectory+"\SelectedWSMetrics\UserChanges"+$CWRun+".csv") -NoTypeInformation 777 | 778 | } 779 | } 780 | sleep -Seconds 1 781 | $queryStatus =Get-CWLQuery -Status Complete -Region $AccessLogsRegion 782 | } 783 | } 784 | 785 | ############################################### 786 | # ! # ! # WorkSpaces Pools Helper # ! # ! # 787 | ############################################### 788 | 789 | function Get-WksPoolsDirectories(){ 790 | param( 791 | $throttleControl 792 | ) 793 | $regions = @('us-east-1','us-west-2', 'ap-south-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ca-central-1', 'eu-central-1','eu-west-1', 'eu-west-2', 'sa-east-1') 794 | $poolsDirectory = New-Object -TypeName Amazon.WorkSpaces.Model.DescribeWorkspaceDirectoriesFilter 795 | $poolsDirectory.Name = "WORKSPACE_TYPE" 796 | $poolsDirectory.Values += "POOLS" 797 | $DeployedPoolsDirectories = @() 798 | # Find regions that have WorkSpaces deployments 799 | foreach($region in $regions){ 800 | $directoryResponse = Get-WKSWorkspaceDirectories -Region $region -limit 25 -Filter $poolsDirectory -NoAutoIteration -select * -NextToken $null 801 | $RegionsCall = $directoryResponse.Directories 802 | $token = $directoryResponse.NextToken 803 | while ($null -ne $token) { 804 | $directoryResponse = Get-WKSWorkspaceDirectories -Region $region -limit 25 -Filter $poolsDirectory -NoAutoIteration -select * -NextToken $token 805 | $RegionsCall += $directoryResponse.Directories 806 | $token = $directoryResponse.NextToken 807 | if($throttleControl){ 808 | Start-Sleep -Milliseconds 200 809 | } 810 | } 811 | if($RegionsCall){ 812 | foreach($PoolsRegion in $RegionsCall){ 813 | $DeployedDirectoriesTemp = New-Object -TypeName PSobject 814 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "Region" -NotePropertyValue $region 815 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "RegistrationCode" -NotePropertyValue $PoolsRegion.RegistrationCode 816 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryId" -NotePropertyValue $PoolsRegion.DirectoryId 817 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryName" -NotePropertyValue $PoolsRegion.DirectoryName 818 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryAlias" -NotePropertyValue $PoolsRegion.Alias 819 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryType" -NotePropertyValue $PoolsRegion.Type 820 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryState" -NotePropertyValue $PoolsRegion.State 821 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryUserEnabledAsLocalAdministrator" -NotePropertyValue $PoolsRegion.WorkspaceCreationProperties.UserEnabledAsLocalAdministrator 822 | $subnetA = Get-EC2Subnet -SubnetId $PoolsRegion.SubnetIds[0] -Region $region 823 | $subnetB = Get-EC2Subnet -SubnetId $PoolsRegion.SubnetIds[1] -Region $region 824 | $dirAvailableIPs = $subnetA.AvailableIpAddressCount + $subnetB.AvailableIpAddressCount 825 | $DeployedDirectoriesTemp | Add-Member -NotePropertyName "DirectoryAvailableIPs" -NotePropertyValue $dirAvailableIPs 826 | $DeployedPoolsDirectories += $DeployedDirectoriesTemp 827 | } 828 | }else{ 829 | Write-Host "Skipping $region" 830 | } 831 | } 832 | return $DeployedPoolsDirectories 833 | } 834 | 835 | function Get-WksPools(){ 836 | param( 837 | $DeployedRegions, 838 | $bundles, 839 | $throttleControl 840 | ) 841 | 842 | $poolsDB = @() 843 | foreach($region in $DeployedRegions.Region){ 844 | $poolsResponse = Get-WKSWorkspacesPool -Region $region -limit 25 -NoAutoIteration -select * -NextToken $null 845 | $pools = $poolsResponse.WorkspacesPools 846 | $token = $poolsResponse.NextToken 847 | while ($null -ne $token) { 848 | $poolsResponse = Get-WKSWorkspacesPool -Region $region -limit 25 -NoAutoIteration -select * -NextToken $token 849 | $pools += $poolsResponse.WorkspacesPools 850 | $token = $poolsResponse.NextToken 851 | if($throttleControl){ 852 | Start-Sleep -Milliseconds 200 853 | } 854 | } 855 | $poolsDB += $pools 856 | } 857 | return $poolsDB 858 | } 859 | 860 | 861 | function Import-WksPoolsSessions(){ 862 | # Description 863 | param( 864 | [String]$poolId, 865 | [String]$region, 866 | $throttleControl 867 | ) 868 | 869 | $poolsSessions = @() 870 | $poolsResponse = Get-WKSWorkspacesPoolSession -PoolId $poolId -Region $region -limit 25 -NoAutoIteration -select * -NextToken $null 871 | $poolsSessions = $poolsResponse.Sessions 872 | $token = $poolsResponse.NextToken 873 | while ($null -ne $token) { 874 | $poolsResponse = Get-WKSWorkspacesPoolSession -PoolId $poolId -Region $region -limit 25 -NoAutoIteration -select * -NextToken $token 875 | $poolsSessions += $poolsResponse.Sessions 876 | $token = $poolsResponse.NextToken 877 | if($throttleControl){ 878 | Start-Sleep -Milliseconds 200 879 | } 880 | } 881 | return $poolsSessions 882 | } 883 | 884 | 885 | ############################################### 886 | # ! # ! # AppStream Helper # ! # ! # 887 | ############################################### 888 | 889 | function Import-AppStreamRegions(){ 890 | param( 891 | $throttleControl 892 | ) 893 | #description 894 | $responseStacks = @() 895 | $regions = @('us-east-1', 'us-east-2', 'us-west-2', 'ap-south-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ca-central-1', 'eu-central-1','eu-west-1', 'eu-west-2') 896 | foreach ($region in $regions){ 897 | $tempStacks = $null 898 | $tempStacks = Get-APSStackList -Region $region -NoAutoIteration -select * -NextToken $null 899 | if(($tempStacks.Stacks).Count -ne 0){ 900 | $responseStacks += $tempStacks.Stacks 901 | $token = $tempStacks.NextToken 902 | while ($null -ne $token) { 903 | $tempStacks = Get-APSStackList -Region $region -NoAutoIteration -select * -NextToken $token 904 | $responseStacks += $tempStacks.Stacks 905 | $token = $tempStacks.NextToken 906 | if($throttleControl){ 907 | Start-Sleep -Milliseconds 200 908 | } 909 | } 910 | }else{ 911 | Write-Host "Skipping $region" 912 | } 913 | } 914 | return $responseStacks 915 | } 916 | 917 | function Import-AppStreamSessions(){ 918 | # Description 919 | param( 920 | [String]$stackName, 921 | [String]$fleetName, 922 | [String]$region, 923 | $capacityInfo, 924 | $throttleControl 925 | ) 926 | 927 | $sessionList = @() 928 | # Get fleet and check capacity 929 | $sessionResponse = Get-APSSessionList -StackName $stackName -FleetName $fleetName -Region $region -limit 50 -NoAutoIteration -select * -NextToken $null 930 | if (($sessionResponse.Sessions).Count -ne 0){ 931 | $sessionList = $sessionResponse.Sessions 932 | $token = $sessionResponse.NextToken 933 | while ($null -ne $token) { 934 | $sessionResponse += Get-APSSessionList -StackName $stackName -FleetName $fleetName -Region $region -limit 50 -NoAutoIteration -select * -NextToken $sessionResponse.NextToken 935 | $sessionList += $sessionResponse.Sessions 936 | $token = $sessionResponse.NextToken 937 | if($throttleControl){ 938 | Start-Sleep -Milliseconds 200 939 | } 940 | } 941 | } 942 | return $sessionList 943 | } -------------------------------------------------------------------------------- /Assets/EUCToolkit-MainGUI.xml: -------------------------------------------------------------------------------- 1 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |