├── 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 |
153 | |-Assets/ 154 | |-CWHelper/ 155 | |-SelectedWSMetrics/ 156 | |-WorkSpacesConnectionSummaryTemplate.json 157 | |-WorkSpacesUDPPacketLoss-Start.png 158 | |-WorkSpacesUDPTemplate.JSON 159 | |-WorkSpacesHistoricalLatency-Start.png 160 | |-WorkSpacesHistoricalLatencyTemplate.JSON 161 | |-WorkSpacesCPU-Start.png 162 | |-WorkSpacesCPUTemplate.JSON 163 | |-WorkSpacesDisk-Start.png 164 | |-WorkSpacesDiskTemplate.JSON 165 | |-WorkSpacesMemory-Start.png 166 | |-WorkSpacesMemoryTemplate.JSON 167 | |-WorkSpacesSessionLaunch-Start.png 168 | |-WorkSpacesSessionLaunchTemplate.JSON 169 | |-EUCToolkit-Helper.psm1 170 | |-EUCToolkit-MainGUI.xml 171 | |-Start-EUCToolkit.ps1 172 | |-CONTRIBUTING.md 173 | |-LICENSE.txt 174 | |-NOTICE.txt 175 | |-README.md 176 |177 | 178 | 179 | ################################################ 180 | 181 | 182 | # License 183 | 184 | This library is licensed under the MIT-0 License. See the LICENSE file. 185 | See license [here](https://github.com/aws-samples/euc-toolkit/blob/main/LICENSE). -------------------------------------------------------------------------------- /Start-EUCToolkit.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | software and associated documentation files (the "Software"), to deal in the Software 6 | without restriction, including without limitation the rights to use, copy, modify, 7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | #> 17 | 18 | <# 19 | .SYNOPSIS 20 | This script contains all of the logic for the EUC Toolkit GUI to operate. 21 | .DESCRIPTION 22 | This script is the main script to initialize the EUC Toolkit GUI. Many actions are offloaded 23 | to the EUCToolkit-Helper module. For more information, see the link below: 24 | https://github.com/aws-samples/euc-toolkit 25 | #> 26 | 27 | Write-Host "Please wait while the EUC Toolkit Initializes" 28 | Write-Host "Importing AWS Tools Modules" 29 | Import-Module AWS.Tools.Common,AWS.Tools.EC2,AWS.Tools.Workspaces,AWS.Tools.Appstream,AWS.Tools.Cloudwatch,AWS.Tools.CloudwatchLogs,AWS.Tools.ServiceQuotas 30 | Write-Host "Importing helper module" 31 | $env:PSModulePath = "$env:PSModulePath;$($PSScriptRoot+"\Assets\EUCToolkit-Helper.psm1")" 32 | Import-Module -Name $($PSScriptRoot+"\Assets\EUCToolkit-Helper.psm1") -Force 33 | add-type -AssemblyName System.Windows.Forms 34 | [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 35 | 36 | # XML Loader for GUI 37 | [xml]$WksMainXaml = Get-Content $($PSScriptRoot+"\Assets\EUCToolkit-MainGUI.xml") 38 | # Read XAML 39 | $reader=(New-Object System.Xml.XmlNodeReader $WksMainXaml) 40 | try{$WksMainForm=[Windows.Markup.XamlReader]::Load( $reader )} 41 | catch{Write-Host "Unable to load Windows.Markup.XamlReader"; exit} 42 | 43 | # Store Form Objects In PowerShell 44 | $WksMainXaml.SelectNodes("//*[@Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $WksMainForm.FindName($_.Name)} 45 | 46 | # Checks credentials. If manually set, it will continue. If not, it will check for an instance profile 47 | # using a IMDSv2 token. For more information, see the link below: 48 | # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html 49 | if(Get-AWSCredential){ 50 | $lblPermissions.Content = "Profile Manually Set" 51 | $global:InstanceProfile = $false 52 | }elseif(Test-Path env:AWS_ACCESS_KEY_ID) { 53 | $lblPermissions.Content = "Utilizing Environment Variables" 54 | $global:InstanceProfile = $false 55 | }else{ 56 | $global:InstanceProfile = $true 57 | try{ 58 | # IMDSv2 Method 59 | [string]$token = Invoke-RestMethod -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "21600"} -Method PUT -Uri http://169.254.169.254/latest/api/token 60 | $TestInstanceProfile = Invoke-RestMethod -Headers @{"X-aws-ec2-metadata-token" = $token} -Method GET -Uri http://169.254.169.254/latest/meta-data/iam/info 61 | }catch{ 62 | $global:InstanceProfile = $false 63 | Show-MessageError -message "No set credentials or Instance Profile detected." -title "Error Retrieving Credentials" 64 | } 65 | if($InstanceProfile -eq $true){ 66 | $lblPermissions.Content = "Utilizing Instance Profile" 67 | }else{ 68 | $lblPermissions.Content = "Undefined Profile (default)" 69 | } 70 | } 71 | 72 | #Install-AWSToolsModule AWS.Tools.Common,AWS.Tools.EC2,AWS.Tools.Workspaces,AWS.Tools.Appstream,AWS.Tools.Cloudwatch,AWS.Tools.CloudwatchLogs,AWS.Tools.ServiceQuotas -CleanUp -Force 73 | 74 | ############################# 75 | # ! # ! # FUNCTIONS # ! # ! # 76 | ############################# 77 | 78 | # This function updates the PowerShell object that acts as the local db and the counter 79 | function Update-WorkSpaceObject(){ 80 | write-host "Getting a list of regions and directories where WorkSpaces are deployed" 81 | $global:WorkSpacesDirectoryDB = Get-WksDirectories -throttleControl $true 82 | write-host "Getting a list of WorkSpaces deployed" 83 | $global:WorkSpacesDB = Get-LocalWorkSpacesDB -DeployedRegions $global:WorkSpacesDirectoryDB -throttleControl $true 84 | write-host "Getting a list of WorkSpaces Bundles" 85 | $deployedRegions = $global:WorkSpacesDB | Select-Object Region -Unique 86 | $global:WorkSpacesBundles = Get-AllBundles -Custom $true -throttleControl $true -Regions $deployedRegions 87 | write-host "Getting WorkSpaces Service Quotas" 88 | $global:WorkSpacesServiceQuotaDB = Get-WksServiceQuotasDB -DeployedRegions $global:WorkSpacesDirectoryDB 89 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 90 | $lblLastDBUpdateBulk.Content = $date 91 | $lblLastDBUpdate.Content = $date 92 | Update-Counter 93 | write-host "WorkSpaces queries are complete" 94 | } 95 | 96 | # This function filters the local db object in real time so that your search criteria is immediately applied 97 | function Search-WorkSpaces(){ 98 | $SearchResults.Items.Clear() 99 | $filtered = $global:WorkSpacesDB | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 100 | if($FirstName.Text -ne ""){ 101 | $filtered = $filtered | Where-Object { ($_.FirstName -like ($FirstName.Text + "*")) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 102 | } 103 | if($LastName.Text -ne ""){ 104 | $filtered = $filtered | Where-Object { ($_.LastName -like ($LastName.Text + "*")) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 105 | } 106 | if($Email.Text -ne ""){ 107 | $filtered = $filtered | Where-Object { ($_.Email -like ("*" + $Email.Text + "*")) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 108 | } 109 | if($txtComputerName.Text -ne ""){ 110 | $filtered = $filtered | Where-Object { ($_.ComputerName -like ("*" + $txtComputerName.Text + "*")) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 111 | } 112 | if($txtUserName.Text -ne ""){ 113 | $filtered = $filtered | Where-Object { ($_.UserName -like ("*" + $txtUserName.Text + "*")) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 114 | } 115 | if($cmboProtocol.SelectedItem -ne "All"){ 116 | $filtered = $filtered | Where-Object { ($_.Protocol -like ($cmboProtocol.SelectedItem)) } | Select-Object WorkSpaceId,Region,UserName,FirstName,LastName,ComputerName,Email,Protocol 117 | } 118 | 119 | foreach($workspace in $filtered){ 120 | if($NULL -ne $workspace.WorkSpaceId){ 121 | $SearchResults.items.Add($workspace) | Out-Null 122 | } 123 | } 124 | } 125 | 126 | # This function finds all of your registered WorkSpaces directories 127 | function Get-BulkDirectories(){ 128 | $selectWKSDirectory.Items.Clear() 129 | $selectWKSDirectory.Items.Add("All Directories") 130 | $Directories = $global:WorkSpacesDB | Select-Object directoryId, Region -Unique | Where-Object { ($_.Region -eq $selectWKSRegion.SelectedItem)} 131 | foreach ($Directory in $Directories ){ 132 | $directorySTR=$Directory.directoryId 133 | $selectWKSDirectory.Items.Add($directorySTR) 134 | } 135 | $selectWKSDirectory.SelectedIndex=0 136 | } 137 | 138 | # This function filters the local db object to reflect your search criteria in the bulk tab 139 | function Get-ImpactedWS(){ 140 | $lstImpactedWorkSpaces.items.Clear() 141 | $bulkdirectoryId = $selectWKSDirectory.SelectedItem 142 | if($NULL -eq $selectWKSBundle.SelectedItem){ 143 | $selectWKSBundle.items.add("Select Bundle") 144 | $selectWKSBundle.SelectedIndex=0 145 | } 146 | $allImpactedWS = $global:WorkSpacesDB | Where-Object { ($_.Region -eq $selectWKSRegion.SelectedItem)} | Select-Object directoryId, WorkSpaceId,UserName,Region,FirstName,LastName,ComputerName,Email,WorkspaceProperties,State,Protocol,BundleId 147 | if($bulkdirectoryId -like "All Directories"){ 148 | $allImpactedWS = $allImpactedWS 149 | }else{ 150 | $allImpactedWS = $allImpactedWS | Where-Object { ($_.directoryId -eq $bulkdirectoryId) } 151 | } 152 | if($selectRunningModeFilterCombo.SelectedItem -ne "Select Running Mode"){ 153 | $allImpactedWS = $allImpactedWS | Where-Object { ($_.WorkspaceProperties.RunningMode -like ($selectRunningModeFilterCombo.SelectedItem))} 154 | } 155 | if($selectWKSBundle.SelectedItem -ne "Select Bundle"){ 156 | $bundleSTR = ($selectWKSBundle.SelectedItem.split(" "))[0] 157 | $allImpactedWS = $allImpactedWS | Where-Object { ($_.BundleId -like ($bundleSTR))} 158 | } 159 | if($cmboBulkProtocol.SelectedIndex -ne -1 -and $cmboBulkProtocol.SelectedIndex -ne 0){ 160 | $protocolSTR = ($cmboBulkProtocol.SelectedItem.split(" "))[0] 161 | $allImpactedWS = $allImpactedWS | Where-Object { ($_.Protocol -like ($protocolSTR))} 162 | } 163 | foreach ($impactedWS in $allImpactedWS){ 164 | if($NULL -ne $impactedWS.WorkSpaceId -and $lstImpactedWorkSpaces.Items.WorkSpaceId -notcontains $impactedWS.WorkSpaceId){ 165 | $lstImpactedWorkSpaces.Items.Add($impactedWS) 166 | } 167 | } 168 | } 169 | 170 | # This function logs your action in the logging tab (does not persist GUI sessions) 171 | function Write-Logger(){ 172 | param( 173 | [String]$message 174 | ) 175 | $logEntry = New-Object -TypeName PSobject 176 | $logDate = Get-Date -Format "MM/dd/yyyy HH:mm K" 177 | $logEntry | Add-Member -NotePropertyName "loggingTime" -NotePropertyValue $logDate 178 | $logEntry | Add-Member -NotePropertyName "loggingMessage" -NotePropertyValue $message 179 | $lstLogging.items.Add($logEntry) | Out-Null 180 | } 181 | 182 | # This function counts and shows your current WorkSpaces counts (Total, Available, and Stopped) 183 | function Update-Counter(){ 184 | $total = $global:WorkSpacesDB.WorkSpaceId.Count 185 | $available = ($global:WorkSpacesDB | Where-Object { $_.State -eq "AVAILABLE"}).Count 186 | $stopped = ($global:WorkSpacesDB | Where-Object { $_.State -eq "STOPPED"}).Count 187 | $PCoIP = ($global:WorkSpacesDB | Where-Object { $_.Protocol -eq "PCOIP"}).Count 188 | $DCV = ($global:WorkSpacesDB | Where-Object { $_.Protocol -eq "DCV"}).Count 189 | $BYOP = ($global:WorkSpacesDB | Where-Object { $_.Protocol -eq "BYOP"}).Count 190 | if($total -eq 0 -or $NULL -eq $total){ 191 | $total = 0 192 | } 193 | if($available -eq 0 -or $NULL -eq $available){ 194 | $available = 0 195 | } 196 | if($stopped -eq 0 -or $NULL -eq $stopped){ 197 | $stopped = 0 198 | } 199 | if($PCoIP -eq 0 -or $NULL -eq $PCoIP){ 200 | $PCoIP = 0 201 | } 202 | if($DCV -eq 0 -or $NULL -eq $DCV){ 203 | $DCV = 0 204 | } 205 | if($BYOP -eq 0 -or $NULL -eq $BYOP){ 206 | $BYOP = 0 207 | } 208 | $TotalWorkSpacesCount.content = $total 209 | $TotalAvailable_Count.content = $available 210 | $TotalStopped_Count.content = $stopped 211 | $lblBulkPCOIPCounter.content = $PCoIP 212 | $lblBulkWSPCounter.content = $DCV 213 | $lblBulkBYOPCounter.content = $BYOP 214 | } 215 | 216 | ############################################### 217 | # ! # ! # WorkSpaces Main GUI Actions # ! # ! # 218 | ############################################### 219 | # This section's actions correspond with a GUI button action. The button objects below are 220 | # created from objects outlined within the XML. 221 | 222 | $FirstName.Add_TextChanged({ 223 | Search-WorkSpaces 224 | }) 225 | $LastName.Add_TextChanged({ 226 | Search-WorkSpaces 227 | }) 228 | $Email.Add_TextChanged({ 229 | Search-WorkSpaces 230 | }) 231 | $txtComputerName.Add_TextChanged({ 232 | Search-WorkSpaces 233 | }) 234 | $txtUserName.Add_TextChanged({ 235 | Search-WorkSpaces 236 | }) 237 | 238 | $btnUpdateData.Add_Click({ 239 | $btnUpdateData.Content="Running..." 240 | $btnUpdateData.IsEnabled=$false 241 | 242 | Update-WorkSpaceObject 243 | Search-WorkSpaces 244 | 245 | $btnUpdateData.content="Refresh" 246 | $btnUpdateData.IsEnabled=$true 247 | }) 248 | 249 | $cmboProtocol.add_SelectionChanged({ 250 | Search-WorkSpaces 251 | }) 252 | 253 | $btnRemoteAssist.Add_Click({ 254 | Invoke-RemoteAssist -privateIP $IPValue.Content 255 | Write-Logger -message "Remote Assist Initiated on " + $IPValue.Content 256 | }) 257 | 258 | $btnUpdateComputeType.Add_Click({ 259 | $WorkSpaceId = $WorkSpaceIdValue.Content 260 | $UpdateComputeReq = New-Object -TypeName PSobject 261 | $UpdateComputeReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 262 | $UpdateComputeReq | Add-Member -NotePropertyName "CurrentCompute" -NotePropertyValue ($global:WorkSpacesDB | Where-Object {$_.WorkspaceId -eq $WorkSpaceId}).WorkspaceProperties.ComputeTypeName 263 | $UpdateComputeReq | Add-Member -NotePropertyName "TargetCompute" -NotePropertyValue $cmboComputeValue.SelectedItem 264 | $UpdateComputeReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 265 | if($UpdateComputeReq.CurrentCompute -ne $UpdateComputeReq.TargetCompute){ 266 | $Output = Update-ComputeType -ComputeReq $UpdateComputeReq 267 | if($Output.ErrorCode){ 268 | $ErrCode = $Output.ErrorCode.ToString() 269 | $ErrMsg = $Output.ErrorMessage.ToString() 270 | Write-Logger -message "Compute change failed for WorkSpaceId $WorkSpaceId Details below:" 271 | Write-Logger -message "Error Code: $ErrCode" 272 | Write-Logger -message "Error Message: $ErrMsg" 273 | Show-MessageError -message "Error during Compute change, see log tab for details" -title "Error Changing Compute Type" 274 | }else{ 275 | Show-MessageSuccess -message "Compute Type update on $WorkSpaceId executed successfully" -title "Successfully Updated Compute Type" 276 | Write-Logger -message "Compute Type update on $WorkSpaceId executed successfully" 277 | } 278 | }else{ 279 | $tmpCompute = $cmboComputeValue.SelectedItem 280 | Show-MessageSuccess -message "Compute Type for $WorkSpaceId is already $tmpCompute" -title "No Changes to Compute Type" 281 | } 282 | }) 283 | 284 | $btnUpdateRootVolume.Add_Click({ 285 | $WorkSpaceId = $WorkSpaceIdValue.Content 286 | $UpdateRootVolReq = New-Object -TypeName PSobject 287 | $UpdateRootVolReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 288 | $UpdateRootVolReq | Add-Member -NotePropertyName "CurrentRootStorage" -NotePropertyValue $RootValue.WorkspaceProperties.RootVolumeSizeGib 289 | $UpdateRootVolReq | Add-Member -NotePropertyName "CurrentUserStorage" -NotePropertyValue $UserValue.Content 290 | $UpdateRootVolReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 291 | 292 | $Output = Update-RootVolume -WorkSpaceReq $UpdateRootVolReq 293 | if($Output.ErrorCode){ 294 | $ErrCode = $Output.ErrorCode.ToString() 295 | $ErrMsg = $Output.ErrorMessage.ToString() 296 | Write-Logger -message "Root volume extention failed for WorkSpaceId $WorkSpaceId Details below:" 297 | Write-Logger -message "Error Code: $ErrCode" 298 | Write-Logger -message "Error Message: $ErrMsg" 299 | Show-MessageError -message "Error during Root volume extention, see log tab for details" -title "Error Extending Root Volume" 300 | }else{ 301 | Show-MessageSuccess -message "Root volume extention on $WorkSpaceId executed successfully" -title "Successfully Extended Root Volume" 302 | Write-Logger -message "Root volume extention on $WorkSpaceId executed successfully" 303 | } 304 | }) 305 | 306 | $btnUpdateUserVolume.Add_Click({ 307 | $WorkSpaceId = $WorkSpaceIdValue.Content 308 | $UpdateUserVolReq = New-Object -TypeName PSobject 309 | $UpdateUserVolReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 310 | $UpdateUserVolReq | Add-Member -NotePropertyName "CurrentRootStorage" -NotePropertyValue $RootValue.WorkspaceProperties.UserVolumeSizeGib 311 | $UpdateUserVolReq | Add-Member -NotePropertyName "CurrentUserStorage" -NotePropertyValue $UserValue.Content 312 | $UpdateUserVolReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 313 | 314 | $Output = Update-UserVolume -WorkSpaceReq $UpdateUserVolReq 315 | if($Output.ErrorCode){ 316 | $ErrCode = $Output.ErrorCode.ToString() 317 | $ErrMsg = $Output.ErrorMessage.ToString() 318 | Write-Logger -message "User volume extention failed for WorkSpaceId $WorkSpaceId Details below:" 319 | Write-Logger -message "Error Code: $ErrCode" 320 | Write-Logger -message "Error Message: $ErrMsg" 321 | Show-MessageError -message "Error during User volume extention, see log tab for details" -title "Error Extending User Volume" 322 | }else{ 323 | Show-MessageSuccess -message "User volume extention on $WorkSpaceId executed successfully" -title "Successfully Extended User Volume" 324 | Write-Logger -message "User volume extention on $WorkSpaceId executed successfully" 325 | } 326 | }) 327 | 328 | $btnTerminateWS.Add_Click({ 329 | $WorkSpaceId = $WorkSpaceIdValue.Content 330 | $wshell = New-Object -ComObject Wscript.Shell 331 | $response = $wshell.Popup("Are you sure you would like to terminate $WorkSpaceId? This cannot be undone.",0,"Alert",64+4) 332 | if($response -eq 6){ 333 | Write-Logger -message "Executing Terminate on WorkSpaceId $WorkSpaceId" 334 | $filteredWS = @() 335 | $filteredWS += $global:WorkSpacesDB | Where-Object { ($_.WorkSpaceId -like $WorkSpaceId) } | Select-Object WorkSpaceId,Region 336 | $TermReq = New-Object -TypeName PSobject 337 | $TermReq | Add-Member -NotePropertyName "TermList" -NotePropertyValue $filteredWS 338 | $Output = Optimize-APIRequest -requestInfo $TermReq -APICall "Remove-WKSWorkspace" 339 | if($Output.ErrorCode){ 340 | $ErrCode = $Output.ErrorCode.ToString() 341 | $ErrMsg = $Output.ErrorMessage.ToString() 342 | Write-Logger -message "Terminate failed for WorkSpaceId $WorkSpaceId Details below:" 343 | Write-Logger -message "Error Code: $ErrCode" 344 | Write-Logger -message "Error Message: $ErrMsg" 345 | Show-MessageError -message "Error Terminating, see log tab for details" -title "Error Terminating WorkSpace" 346 | }else{ 347 | Show-MessageSuccess -message "Terminate API on $WorkSpaceId executed successfully" -title "Successfully Terminated WorkSpace" 348 | Write-Logger -message "Terminate API on $WorkSpaceId executed successfully" 349 | } 350 | } 351 | }) 352 | 353 | $btnRebuildWS.Add_Click({ 354 | $WorkSpaceId = $WorkSpaceIdValue.Content 355 | Write-Logger -message "Executing Rebuild on WorkSpaceId $WorkSpaceId" 356 | $ResetReq = New-Object -TypeName PSobject 357 | $ResetReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 358 | $ResetReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 359 | $Output = Optimize-APIRequest -requestInfo $ResetReq -APICall "Reset-WKSWorkspace" 360 | if($Output.ErrorCode){ 361 | $ErrCode = $Output.ErrorCode.ToString() 362 | $ErrMsg = $Output.ErrorMessage.ToString() 363 | Write-Logger -message "Rebuild failed for WorkSpaceId $WorkSpaceId Details below:" 364 | Write-Logger -message "Error Code: $ErrCode" 365 | Write-Logger -message "Error Message: $ErrMsg" 366 | Show-MessageError -message "Error Rebuilding, see log tab for details" -title "Error Rebuilding WorkSpace" 367 | }else{ 368 | Show-MessageSuccess -message "Rebuild API on $WorkSpaceId executed successfully" -title "Successfully Rebuilt WorkSpace" 369 | Write-Logger -message "Rebuild API on $WorkSpaceId executed successfully" 370 | } 371 | }) 372 | 373 | $btnRestoreWS.Add_Click({ 374 | $WorkSpaceId = $WorkSpaceIdValue.Content 375 | Write-Logger -message "Executing Restore on WorkSpaceId $WorkSpaceId" 376 | $ResetReq = New-Object -TypeName PSobject 377 | $ResetReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 378 | $ResetReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 379 | $Output = Optimize-APIRequest -requestInfo $ResetReq -APICall "Restore-WKSWorkspace" 380 | if($Output.ErrorCode){ 381 | $ErrCode = $Output.ErrorCode.ToString() 382 | $ErrMsg = $Output.ErrorMessage.ToString() 383 | Write-Logger -message "Restore failed for WorkSpaceId $WorkSpaceId Details below:" 384 | Write-Logger -message "Error Code: $ErrCode" 385 | Write-Logger -message "Error Message: $ErrMsg" 386 | Show-MessageError -message "Error Restoring, see log tab for details" -title "Error Restoring WorkSpace" 387 | }else{ 388 | Show-MessageSuccess -message "Restore API on $WorkSpaceId executed successfully" -title "Successfully Restored WorkSpace" 389 | Write-Logger -message "Restore API on $WorkSpaceId executed successfully" 390 | } 391 | }) 392 | 393 | $btnPowerUpWS.Add_Click({ 394 | $WorkSpaceId =$WorkSpaceIdValue.Content 395 | Write-Logger -message "Executing Start on WorkSpaceId $WorkSpaceId" 396 | $StartReq = New-Object -TypeName PSobject 397 | $StartReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 398 | $StartReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 399 | $Output = Optimize-APIRequest -requestInfo $StartReq -APICall "Start-WKSWorkspace" 400 | if($Output.ErrorCode){ 401 | $ErrCode = $Output.ErrorCode.ToString() 402 | $ErrMsg = $Output.ErrorMessage.ToString() 403 | Write-Logger -message "Start failed for WorkSpaceId $WorkSpaceId Details below:" 404 | Write-Logger -message "Error Code: $ErrCode" 405 | Write-Logger -message "Error Message: $ErrMsg" 406 | Show-MessageError -message "Error starting, see log tab for details" -title "Error Starting WorkSpace" 407 | }else{ 408 | Show-MessageSuccess -message "Start API on $WorkSpaceId executed successfully" -title "Successfully Started WorkSpace" 409 | Write-Logger -message "Start API on $WorkSpaceId executed successfully" 410 | } 411 | }) 412 | 413 | $btnPowerDownWS.Add_Click({ 414 | $WorkSpaceId = $WorkSpaceIdValue.Content 415 | Write-Logger -message "Executing Stop on WorkSpaceId $WorkSpaceId" 416 | $StopReq = New-Object -TypeName PSobject 417 | $StopReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 418 | $StopReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 419 | $Output = Optimize-APIRequest -requestInfo $StopReq -APICall "Stop-WKSWorkspace" 420 | if($Output.ErrorCode){ 421 | $ErrCode = $Output.ErrorCode.ToString() 422 | $ErrMsg = $Output.ErrorMessage.ToString() 423 | Write-Logger -message "Stop failed for WorkSpaceId $WorkSpaceId Details below:" 424 | Write-Logger -message "Error Code: $ErrCode" 425 | Write-Logger -message "Error Message: $ErrMsg" 426 | Show-MessageError -message "Error stopping, see log tab for details" -title "Error Stopping WorkSpace" 427 | }else{ 428 | Show-MessageSuccess -message "Stop API on $WorkSpaceId executed successfully" -title "Successfully Stopped WorkSpace" 429 | Write-Logger -message "Stop API on $WorkSpaceId executed successfully" 430 | } 431 | }) 432 | 433 | $btnRebootWS.Add_Click({ 434 | $WorkSpaceId = $WorkSpaceIdValue.Content 435 | Write-Logger -message "Executing Reboot on WorkSpaceId $WorkSpaceId" 436 | $StopReq = New-Object -TypeName PSobject 437 | $StopReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 438 | $StopReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 439 | $Output = Optimize-APIRequest -requestInfo $StopReq -APICall "Restart-WKSWorkspace" 440 | if($Output.ErrorCode){ 441 | $ErrCode = $Output.ErrorCode.ToString() 442 | $ErrMsg = $Output.ErrorMessage.ToString() 443 | Write-Logger -message "Reboot failed for WorkSpaceId $WorkSpaceId Details below:" 444 | Write-Logger -message "Error Code: $ErrCode" 445 | Write-Logger -message "Error Message: $ErrMsg" 446 | Show-MessageError -message "Error rebooting, see log tab for details" -title "Error Rebooting WorkSpace" 447 | }else{ 448 | Show-MessageSuccess -message "Reboot API on $WorkSpaceId executed successfully" -title "Successfully Rebooted WorkSpace" 449 | Write-Logger -message "Reboot API on $WorkSpaceId executed successfully" 450 | } 451 | }) 452 | 453 | $btnChangeRunningMode.Add_Click({ 454 | $WorkSpaceId = $WorkSpaceIdValue.Content 455 | $RunningModeUpdate = $RunningModeValue.Content 456 | $UpdateRunningReq = New-Object -TypeName PSobject 457 | $UpdateRunningReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 458 | $UpdateRunningReq | Add-Member -NotePropertyName "CurrentRunMode" -NotePropertyValue $RunningModeUpdate 459 | $UpdateRunningReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 460 | $wshell = New-Object -ComObject Wscript.Shell 461 | $response = $wshell.Popup("Are you sure you would like to change the running mode of WorkSpace $WorkSpaceId?",0,"Alert",64+4) 462 | if($response -eq 6){ 463 | Write-Logger -message "Executing update to running mode on selected WorkSpaceId" 464 | $Output = Update-RunningMode -UpdateReq $UpdateRunningReq 465 | if($RunningModeUpdate -eq "AUTO_STOP"){ 466 | $TargetRun = "ALWAYS_ON" 467 | }else{ 468 | $TargetRun = "AUTO_STOP" 469 | } 470 | if($Output.ErrorCode){ 471 | $ErrCode = $Output.ErrorCode.ToString() 472 | $ErrMsg = $Output.ErrorMessage.ToString() 473 | Write-Logger -message "Running Mode change failed for WorkSpaceId $WorkSpaceId. Details below:" 474 | Write-Logger -message "Error Code: $ErrCode" 475 | Write-Logger -message "Error Message: $ErrMsg" 476 | Show-MessageError -message "Error during Running Mode change, see log tab for details" -title "Error Changing Running Mode" 477 | }else{ 478 | Show-MessageSuccess -message "Running Mode update on $WorkSpaceId executed successfully" -title "Successfully Updated Running Mode" 479 | Write-Logger -message "Running Mode update to $TargetRun on $WorkSpaceId executed successfully" 480 | } 481 | } 482 | }) 483 | 484 | $btnCopyWSId.Add_Click({ 485 | write-output $WorkSpaceIdValue.Content | Set-clipboard 486 | }) 487 | 488 | $btnCopyWSIP.Add_Click({ 489 | write-output $IPValue.Content | Set-clipboard 490 | }) 491 | 492 | $btnCopyWSComputerName.Add_Click({ 493 | write-output $ComputerNameValue.Content | Set-clipboard 494 | }) 495 | 496 | $btnBackupUserVolume.Add_Click({ 497 | $WorkspaceId = $WorkSpaceIdValue.Content 498 | $tmpPath = $txtDisk2VHDPath.Text + "Disk2VHD64.exe" 499 | $filePathDisk2VHD = Convert-Path -path $tmpPath 500 | $filePathPSExec = $txtPSExecPath.Text 501 | $backupDestination = $txtBackUpDest.Text + $WorkspaceId + ".vhdx" 502 | $targetIP = $IPValue.Content 503 | if($null -ne $filePathPSExec){ 504 | if($null -ne $txtDisk2VHDPath.Text){ 505 | $tmpFileName = $WorkspaceId+".vhdx" 506 | $arguments = " -s -c \\$targetIP `"$filePathDisk2VHD`" -C D: D:\$tmpFileName -accepteula" 507 | $exe = $filePathPSExec+"psexec.exe" 508 | try{ 509 | start-process $exe $arguments -Wait 510 | $WorkSpaceDisk2VHDLocation = '\\'+$targetIP+'\d$\'+$WorkspaceId+'.VHDX' 511 | Copy-Item -Path $WorkSpaceDisk2VHDLocation -Destination $backupDestination 512 | }Catch{ 513 | $msg = $_ 514 | Write-Logger -message "Failed to backup $WorkspaceId. See error details below:" 515 | Write-Logger -message "$msg" 516 | } 517 | }else{ 518 | Show-MessageError -message "Unable to backup WorkSpace, Disk2VHD path not provided in Admin tab." -title "Disk2VHD Backup Failed" 519 | } 520 | 521 | }else{ 522 | Show-MessageError -message "Unable to backup WorkSpace, PSExec path not provided in Admin tab." -title "PSExec Backup Failed" 523 | } 524 | }) 525 | 526 | $btnGatherLogs.Add_Click({ 527 | $WorkspaceId = $WorkSpaceIdValue.Content 528 | $filePathPSExec = $txtPSExecPath.Text 529 | $targetIP = $IPValue.Content 530 | if($null -ne $filePathPSExec){ 531 | $arguments = ' -i -s \\'+$targetIP+' PowerShell.exe -NoLogo -ExecutionPolicy RemoteSigned -NoProfile -File "C:\Program Files\Amazon\WorkSpacesConfig\Scripts\Get-WorkSpaceLogs.ps1"' 532 | $exe = $filePathPSExec+"psexec.exe" 533 | try{ 534 | start-process $exe $arguments -Wait 535 | $WorkSpaceLogsLocation = '\\'+$targetIP+'\d$\*.zip' 536 | $filePathLogsOutputh = $folderPathLogsOutput + $WorkSpaceId + "\" 537 | if (!(Test-Path $filePathLogsOutputh)){ 538 | New-Item -Path $filePathLogsOutputh -ItemType Directory 539 | } 540 | Copy-Item -Path $WorkSpaceLogsLocation -Destination $filePathLogsOutputh 541 | }Catch{ 542 | $msg = $_ 543 | Write-Logger -message "Failed to gather logs for $WorkspaceId. See error details below:" 544 | Write-Logger -message "$msg" 545 | } 546 | }else{ 547 | Show-MessageError -message "Unable to gather WorkSpace logs, PSExec path not provided in Admin tab." -title "PSExec Log Gather Failed" 548 | } 549 | }) 550 | 551 | $btnRDP.Add_Click({ 552 | $WorkSpaceLookup = $global:WorkSpacesDB | Where-Object { ($_.WorkSpaceId -like $WorkSpaceIdValue.Content) } 553 | if(($WorkSpaceLookup.ComputerName[0] -ne "A") -or ($WorkSpaceLookup.ComputerName[0] -ne "U") -or ($WorkSpaceLookup.ComputerName[0] -ne "R")){ 554 | if($WorkSpaceLookup.State -eq 'STOPPED'){ 555 | $wshell = New-Object -ComObject Wscript.Shell 556 | $response=$wshell.Popup("Warning, WorkSpace is powered off, Would you like it powered on to connect?",0,"",0x1) 557 | }else{ 558 | $connectionInfo = $WorkSpaceLookup.ComputerName 559 | mstsc /v:$connectionInfo 560 | Write-Logger -message "RDP session Initiated on " + $IPValue.Content 561 | } 562 | }else{ 563 | Show-MessageError -message "Unable to RDP to a Linux WorkSpace" -title "Linux WorkSpace Selected" 564 | } 565 | }) 566 | 567 | # Button to Modify Protocol (PCoIP to DCV) 568 | $btnModifyProtocol.Add_Click({ 569 | $WorkSpaceId = $WorkSpaceIdValue.Content 570 | $ProtocolModifyReq = New-Object -TypeName PSobject 571 | $ProtocolModifyReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $WorkSpaceId 572 | $ProtocolModifyReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $RegionValue.Content 573 | $ProtocolModifyReq | Add-Member -NotePropertyName "Protocol" -NotePropertyValue $lblProtocolValue.Content 574 | $Output = Set-WKSProtocol -ProtocolModifyReq $ProtocolModifyReq 575 | if($Output.ErrorCode){ 576 | $ErrCode = $Output.ErrorCode 577 | $ErrMsg = $Output.ErrorMessage 578 | Write-Logger -message "Protocol modification failed $WorkSpaceId. Details below:" 579 | Write-Logger -message "Error Code: $ErrCode" 580 | Write-Logger -message "Error Message: $ErrMsg" 581 | Show-MessageError -message "Error modifying protocol, see log tab for details" -title "Error Modifying Protocol" 582 | }else{ 583 | Show-MessageSuccess -message "Protocol modification on $WorkSpaceId executed successfully" -title "Successfully Modified Protocol" 584 | Write-Logger -message "Protocol modification on $WorkSpaceId executed successfully" 585 | } 586 | 587 | }) 588 | 589 | $btnGetUserExperience.Add_Click({ 590 | $workingDirectory=($PSScriptRoot +"\\Assets\\CWHelper\\") 591 | $global:cloudWatchRun++ 592 | 593 | # Clear out last run 594 | $CloudWatchHistoricalLatency.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesHistoricalLatency-Start.png") 595 | $CloudWatchWorkSpaceLatency.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesUDPPacketLoss-Start.png") 596 | $CloudWatchWorkSpaceLaunch.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesSessionLaunch-Start.png") 597 | $CloudWatchWorkSpaceCPU.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesCPU-Start.png") 598 | $CloudWatchWorkSpaceMemory.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesMemory-Start.png") 599 | $CloudWatchWorkSpaceDisk.Source = ($PSScriptRoot+"\\Assets\\CWHelper\\WorkSpacesDisk-Start.png") 600 | $CloudWatchWorkSpaceCPU.Visibility = "Visible" 601 | $CloudWatchWorkSpaceMemory.Visibility = "Visible" 602 | $CloudWatchWorkSpaceDisk.Visibility = "Visible" 603 | $CloudWatchLoginInfo.items.clear() 604 | $CloudWatchWorkSpaceModifications.items.clear() 605 | 606 | $resources = Get-ChildItem -Path "$workingDirectory\SelectedWSMetrics\" 607 | if($resources.count -ne 0 -and ($global:cloudWatchRun -eq 1)){ 608 | foreach($item in $resources){ 609 | $tmpFileName = $item.Name 610 | Remove-item -path "$workingDirectory\SelectedWSMetrics\$tmpFileName" -force -ErrorAction SilentlyContinue 611 | } 612 | } 613 | 614 | # Move to CloudWatch Tab 615 | $tabCloudWatch.Visibility ="Visible" 616 | $tabControl.SelectedIndex =5 617 | 618 | $CloudWatch_Tick={ 619 | $imageRepo = "$PSScriptRoot\Assets\CWHelper\SelectedWSMetrics" 620 | $resources = Get-ChildItem -Path $imageRepo 621 | if($resources.count -eq $global:ImagesExpected){ 622 | start-sleep -Seconds 1 623 | $CloudWatchHistoricalLatency.Source = ($imageRepo+"\WorkSpacesHistoricalLatency"+$global:cloudWatchRun+".png") 624 | $CloudWatchWorkSpaceLatency.Source = ($imageRepo+"\WorkSpacesUDP"+$global:cloudWatchRun+".png") 625 | $CloudWatchWorkSpaceLaunch.Source = ($imageRepo+"\WorkSpacesConnectionSummary"+$global:cloudWatchRun+".png") 626 | $CloudWatchWorkSpaceCPU.Source = ($imageRepo+"\WorkSpacesCPU"+$global:cloudWatchRun+".png") 627 | $CloudWatchWorkSpaceCPU.Visibility ="Visible" 628 | $CloudWatchWorkSpaceMemory.Source = ($imageRepo+"\WorkSpacesMemory"+$global:cloudWatchRun+".png") 629 | $CloudWatchWorkSpaceMemory.Visibility ="Visible" 630 | $CloudWatchWorkSpaceDisk.Source = ($imageRepo+"\WorkSpacesDisk"+$global:cloudWatchRun+".png") 631 | $CloudWatchWorkSpaceDisk.Visibility = "Visible" 632 | 633 | $global:CloudWatchTimer.Stop() 634 | $global:CloudWatchTimer.Dispose() 635 | } 636 | 637 | if(($CloudWatchLoginInfo.items.Count -eq 0) -and ($resources | Where-Object {$_.Name -like "ConnectionHistory"+$global:cloudWatchRun+".csv"})){ 638 | $global:imagesRetrieved++ 639 | $CSV = Import-Csv ($imageRepo+"\ConnectionHistory"+$global:cloudWatchRun+".csv") 640 | foreach ($record in $CSV){ 641 | $CloudWatchLoginInfo.items.Add($record) 642 | } 643 | } 644 | if(($CloudWatchWorkSpaceModifications.items.Count -eq 0) -and ($resources | Where-Object {$_.Name -like "UserChanges"+$global:cloudWatchRun+".csv"})){ 645 | $global:imagesRetrieved++ 646 | $CSV = Import-Csv ($imageRepo+"\UserChanges"+$global:cloudWatchRun+".csv") 647 | foreach ($record in $CSV){ 648 | $CloudWatchWorkSpaceModifications.items.Add($record) 649 | } 650 | } 651 | } 652 | 653 | # Timer to Watch the Jobs: 654 | $global:CloudWatchTimer = New-Object 'System.Windows.Forms.Timer' 655 | $global:QueryTime=1 656 | 657 | $imageRepo = "$PSScriptRoot\Assets\CWHelper\SelectedWSMetrics" 658 | $resources = Get-ChildItem -Path $imageRepo 659 | $global:ImagesExpected=(3+$resources.count) 660 | 661 | if($txtCloudWatchAccessLogs.Text-ne ""){ 662 | $AccessLogsRegion=($txtCloudWatchAccessLogs.Text-split ":")[3] 663 | $AccessLogsName=($txtCloudWatchAccessLogs.Text-split ":")[6] 664 | $global:ImagesExpected+= 2 665 | 666 | } 667 | 668 | if($global:InstanceProfile -eq $true){ 669 | Get-CloudWatchImagesServiceMetrics -workingDirectory $workingDirectory -WSId $WorkSpaceIdValue.content -AWSProfile $false -region $RegionValue.Content -CWRun $global:cloudWatchRun 670 | 671 | Get-CloudWatchImagesWorkSpaceMetrics -workingDirectory $workingDirectory -WSId $WorkSpaceIdValue.content -region $RegionValue.Content -CWRun $global:cloudWatchRun 672 | $global:ImagesExpected += 3 673 | 674 | Get-CloudWatchStats -workingDirectory $workingDirectory -WorkSpaceAccessLogs $AccessLogsName -CloudTrailLogs $CloudTrailsLogsName -WorkSpaceId $WorkSpaceIdValue.content -AWSProfile $false -CloudTrailRegion $CloudTrailsLogsRegion -AccessLogsRegion $AccessLogsRegion -CWRun $global:cloudWatchRun 675 | }else{ 676 | $CurrentCred = Get-AWSCredential 677 | Get-CloudWatchImagesServiceMetrics -workingDirectory $workingDirectory -WSId $WorkSpaceIdValue.content -AWSProfile $CurrentCred -region $RegionValue.Content -CWRun $global:cloudWatchRun 678 | 679 | Get-CloudWatchImagesWorkSpaceMetrics -workingDirectory $workingDirectory -WSId $WorkSpaceIdValue.content -AWSProfile $CurrentCred -region $RegionValue.Content -CWRun $global:cloudWatchRun 680 | $global:ImagesExpected += 3 681 | 682 | Get-CloudWatchStats -workingDirectory $workingDirectory -WorkSpaceAccessLogs $AccessLogsName -CloudTrailLogs $CloudTrailsLogsName -WorkSpaceId $WorkSpaceIdValue.content -CloudTrailRegion $CloudTrailsLogsRegion -AccessLogsRegion $AccessLogsRegion -CWRun $global:cloudWatchRun 683 | } 684 | 685 | $global:CloudWatchTimer.Enabled = $True 686 | $global:CloudWatchTimer.Interval = 1000 687 | $global:CloudWatchTimer.add_Tick($CloudWatch_Tick) 688 | $global:imagesRetrieved = 0 689 | }) 690 | 691 | # Updates interactive WorkSpaces Help Desk tab 692 | $SearchResults.add_SelectionChanged({ 693 | if ($SearchResults.SelectedIndex -ne -1){ 694 | $tabCloudWatch.Visibility ="Hidden" 695 | $searchedItem = $SearchResults.SelectedItems 696 | $wsID = $searchedItem.WorkSpaceId 697 | $WorkSpaceInfo = $global:WorkSpacesDB | Where-Object { ($_.WorkSpaceId -like $wsID) } 698 | $UserNameValue.Content = $WorkSpaceInfo.UserName 699 | $WorkSpaceIdValue.Content = $WorkSpaceInfo.WorkspaceId 700 | $WorkSpaceComputeType = $WorkSpaceInfo.WorkspaceProperties.ComputeTypeName.Value | Out-String 701 | $WorkSpaceComputeType = $WorkSpaceComputeType.replace("`n","").replace("`r","") 702 | 703 | if($WorkSpaceComputeType.substring(0,1) -ne "G"){ 704 | $cmboComputeValue.Visibility="Visible" 705 | $lblComputeValue.Visibility="Hidden" 706 | $btnUpdateComputeType.Visibility="Visible" 707 | $cmboComputeValue.items.clear() 708 | $cmboComputeValue.items.Add("VALUE") 709 | $cmboComputeValue.items.Add("STANDARD") 710 | $cmboComputeValue.items.Add("PERFORMANCE") 711 | $cmboComputeValue.items.Add("POWER") 712 | $cmboComputeValue.items.Add("POWERPRO") 713 | if(($global:WorkSpacesServiceQuotaDB | Where-Object {$_.Region -eq $WorkSpaceInfo.Region}).quotaWks4xl -ne 0){ 714 | $cmboComputeValue.items.Add("GP.4XL") 715 | } 716 | if(($global:WorkSpacesServiceQuotaDB | Where-Object {$_.Region -eq $WorkSpaceInfo.Region}).quotaWks8xl -ne 0){ 717 | $cmboComputeValue.items.Add("GP.8XL") 718 | } 719 | $cmboComputeValue.SelectedItem = $WorkSpaceComputeType 720 | }else{ 721 | $cmboComputeValue.Visibility="Hidden" 722 | $lblComputeValue.Visibility="Visible" 723 | $btnUpdateComputeType.Visibility="Hidden" 724 | $lblComputeValue.Content = $WorkSpaceComputeType 725 | } 726 | 727 | $RootValue.Content = $WorkSpaceInfo.WorkspaceProperties.RootVolumeSizeGib 728 | $UserValue.Content = $WorkSpaceInfo.WorkspaceProperties.UserVolumeSizeGib 729 | $RunningModeValue.Content = $WorkSpaceInfo.WorkspaceProperties.RunningMode 730 | $IPValue.Content = $WorkSpaceInfo.IPAddress 731 | $RegionValue.Content = $WorkSpaceInfo.Region 732 | $ComputerNameValue.Content = $WorkSpaceInfo.ComputerName 733 | $RegCodeValue.Content = $WorkSpaceInfo.RegCode 734 | $lblStateValue.Content = $WorkSpaceInfo.State 735 | $lblProtocolValue.Content = $WorkSpaceInfo.Protocol 736 | 737 | try{ 738 | $snapDate = Get-Date -Date ((Get-WKSWorkspaceSnapshot -WorkspaceId $WorkSpaceIdValue.Content -Region $RegionValue.Content).RebuildSnapshots.SnapshotTime | Out-String) -Format "MM/dd/yyyy HH:mm" 739 | }catch{ 740 | $snapDate = "Not Available" 741 | } 742 | $lblSnapShotValue.Content = $snapDate 743 | if($WorkSpaceInfo.LastUpdated){ 744 | $lblLastDBUpdate.Content = $WorkSpaceInfo.LastUpdated 745 | }else{ 746 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 747 | $lblLastDBUpdate.Content = $date 748 | } 749 | if(($ComputerNameValue.Content.substring(0,1) -eq "A") -or ($ComputerNameValue.Content.substring(0,1) -eq "U") -or ($ComputerNameValue.Content.substring(0,1) -eq "R")){ 750 | $btnRemoteAssist.Visibility="Hidden" 751 | $btnRDP.Visibility="Hidden" 752 | $btnGatherLogs.Visibility="Hidden" 753 | $btnBackupUserVolume.Visibility="Hidden" 754 | } 755 | else{ 756 | # Windows Machine 757 | $btnRemoteAssist.Visibility="Visible" 758 | $btnRDP.Visibility="Visible" 759 | $btnGatherLogs.Visibility="Visible" 760 | $btnBackupUserVolume.Visibility="Visible" 761 | } 762 | if($WorkSpaceInfo.StandbyWorkspacesProperties){ 763 | $btnMrrEnabled.Content = "Enabled" 764 | if($WorkSpaceInfo.StandbyWorkspacesProperties.DataReplication -eq "PRIMARY_AS_SOURCE"){ 765 | $repSnapTime = ($WorkSpaceInfo.StandbyWorkspacesProperties.RecoverySnapshotTime).ToShortTimeString() 766 | $repSnapDate = ($WorkSpaceInfo.StandbyWorkspacesProperties.RecoverySnapshotTime).ToShortDateString() 767 | $btnMrrReplication.Content = "Replicating: $repSnapDate $repSnapTime" 768 | }else{ 769 | $btnMrrReplication.Content = "Replicating: N/A" 770 | } 771 | 772 | }else{ 773 | $btnMrrEnabled.Content = "N/A" 774 | $btnMrrReplication.Content = "" 775 | } 776 | if($WorkSpaceInfo.WorkspaceProperties.GlobalAccelerator.Mode.Value -eq "INHERITED"){ 777 | $AgaDirSettings = $global:WorkSpacesDirectoryDB | Where-Object {$_.RegistrationCode -eq $WorkSpaceInfo.RegCode} 778 | if($AgaDirSettings.StreamingProperties.GlobalAccelerator.Mode.Value -eq "ENABLED_AUTO"){ 779 | $lblWksAGAValue.Content = "Enabled" 780 | } 781 | elseif(($AgaDirSettings.StreamingProperties.GlobalAccelerator.Mode.Value -eq "DISABLED") -or ([string]::IsNullOrEmpty($AgaDirSettings.StreamingProperties.GlobalAccelerator))){ 782 | $lblWksAGAValue.Content = "Disabled" 783 | } 784 | } 785 | elseif (($WorkSpaceInfo.WorkspaceProperties.GlobalAccelerator.Mode).Value -eq "ENABLED_AUTO") { 786 | $lblWksAGAValue.Content = "Enabled" 787 | }else{ 788 | $lblWksAGAValue.Content = "N/A" 789 | } 790 | } 791 | }) 792 | 793 | ############################################### 794 | # ! # ! # WorkSpaces Bulk GUI Actions # ! # ! # 795 | ############################################### 796 | # This section's bulk actions correspond with a GUI button action. The button objects below are 797 | # created from objects outlined within the XML. 798 | 799 | $selectWKSRegion.add_SelectionChanged({ 800 | $selectWKSDirectory.Items.Clear() 801 | Get-BulkDirectories 802 | $migrateBundleCombo.items.Clear() 803 | $selectWKSBundle.items.Clear() 804 | $migrateBundleCombo.items.add("Select Bundle") 805 | $deployedBundles = $global:WorkSpacesDB | Where-Object { ($_.Region -eq $selectWKSRegion.SelectedItem)} | Select-Object BundleId, Protocol -Unique 806 | if($deployedBundles.count -eq 0){ 807 | $selectWKSBundle.items.add("No Custom Bundles Found") 808 | $migrateBundleCombo.items.add("No Custom Bundles Found") 809 | }else{ 810 | foreach($bundle in $deployedBundles){ 811 | $targetBundle = $global:WorkSpacesBundles | Where-Object { ($_.BundleID -eq $bundle.BundleID)} | Select-Object BundleId, Protocol, Name -Unique 812 | if($null -eq $targetBundle.Name){ 813 | $BundleConcat = $bundle.BundleId + " (No Bundle Name)" 814 | }else{ 815 | $BundleConcat = $bundle.BundleId + " (" + $targetBundle.Name + ")" 816 | } 817 | $selectWKSBundle.items.add("$BundleConcat") 818 | $migrateBundleCombo.items.add("$BundleConcat") 819 | } 820 | } 821 | $migrateBundleCombo.SelectedIndex=0 822 | }) 823 | 824 | # Bulk Buttons 825 | $selectWKSDirectory.add_SelectionChanged({ 826 | Get-ImpactedWS 827 | }) 828 | 829 | $cmboBulkProtocol.add_SelectionChanged({ 830 | Get-ImpactedWS 831 | }) 832 | 833 | $selectWKSBundle.add_SelectionChanged({ 834 | Get-ImpactedWS 835 | }) 836 | 837 | $selectRunningModeFilterCombo.add_SelectionChanged({ 838 | Get-ImpactedWS 839 | }) 840 | 841 | $btnPowerOn.Add_Click({ 842 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 843 | $ImpactedList = $lstImpactedWorkSpaces.Items 844 | }else{ 845 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 846 | } 847 | 848 | $filteredList = @() 849 | foreach($WS in $ImpactedList){ 850 | if($WS.RunningMode -eq "ALWAYS_ON"){ 851 | $WS = $WS.WorkSpaceId 852 | Write-logger -message "$WS is AlwaysOn, skipping in API request." 853 | }else{ 854 | $filteredList += $WS 855 | } 856 | } 857 | $logs = Optimize-APIRequest -requestInfo $filteredList -APICall "Start-WKSWorkspace" 858 | if($logs.ErrorCode){ 859 | $ErrCode = $logs.ErrorCode 860 | $ErrMsg = $logs.ErrorMessage 861 | Write-Logger -message "Start failed for some or all of the selected WorkSpaces. Details below:" 862 | Write-Logger -message "Error Code: $ErrCode" 863 | Write-Logger -message "Error Message: $ErrMsg" 864 | Show-MessageError -message "Error starting, see log tab for details" -title "Error Stopping WorkSpaces" 865 | }else{ 866 | Show-MessageSuccess -message "Start API on selected WorkSpaces executed successfully" -title "Successfully Started WorkSpace" 867 | Write-Logger -message "Start API on selected WorkSpaces executed successfully" 868 | } 869 | }) 870 | 871 | $btnPowerDown.Add_Click({ 872 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 873 | $ImpactedList = $lstImpactedWorkSpaces.Items 874 | }else{ 875 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 876 | } 877 | 878 | $filteredList = @() 879 | foreach($WS in $ImpactedList){ 880 | if($WS.RunningMode -eq "ALWAYS_ON"){ 881 | $WS = $WS.WorkSpaceId 882 | Write-logger -message "$WS is AlwaysOn, skipping in API request." 883 | }else{ 884 | $filteredList += $WS 885 | } 886 | } 887 | $logs = Optimize-APIRequest -requestInfo $filteredList -APICall "Stop-WKSWorkspace" 888 | if($logs.ErrorCode){ 889 | $ErrCode = $logs.ErrorCode 890 | $ErrMsg = $logs.ErrorMessage 891 | Write-Logger -message "Stop failed for some or all of the selected WorkSpaces. Details below:" 892 | Write-Logger -message "Error Code: $ErrCode" 893 | Write-Logger -message "Error Message: $ErrMsg" 894 | Show-MessageError -message "Error stopping, see log tab for details" -title "Error Stopping WorkSpaces" 895 | }else{ 896 | Show-MessageSuccess -message "Stop API on selected WorkSpaces executed successfully" -title "Successfully Started WorkSpace" 897 | Write-Logger -message "Stop API on selected WorkSpaces executed successfully" 898 | } 899 | }) 900 | 901 | $btnMigrate.Add_Click({ 902 | if(($migrateBundleTxt.Text -like "Select Bundle") -or($migrateBundleTxt.Text -like "No Custom Bundles Found")){ 903 | Show-MessageError -message "Please select a valid bundle from the dropdown before initiating." -title "Error Migrating WorkSpace" 904 | }else{ 905 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 906 | $ImpactedList = $lstImpactedWorkSpaces.Items 907 | }else{ 908 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 909 | } 910 | foreach ($wks in $ImpactedList){ 911 | $bundleSTR = ($migrateBundleCombo.SelectedItem.split(" "))[0] 912 | $migrateReq = New-Object -TypeName PSobject 913 | $migrateReq | Add-Member -NotePropertyName "WorkSpaceId" -NotePropertyValue $Wks.WorkSpaceId 914 | $migrateReq | Add-Member -NotePropertyName "BundleId" -NotePropertyValue $bundleSTR 915 | $migrateReq | Add-Member -NotePropertyName "Region" -NotePropertyValue $selectWKSRegion.selectedItem 916 | 917 | $Output = Initialize-MigrateWorkSpace -requestInfo $migrateReq 918 | if($NULL -eq $Output -or $Output.SourceWorkspaceId.Count -ne 1){ 919 | $errMsg = "Error Message: Was unable to migrate " + $Wks.WorkSpaceId 920 | Write-Logger -message $errMsg 921 | Show-MessageError -message "Error Migrating, see log tab for details" -title "Error Migrating WorkSpace" 922 | }else{ 923 | $WksId = $Output.SourceWorkspaceId.ToString() 924 | Write-Logger -message "Migrate successfully initiated for WorkSpaceId $WksId" 925 | Show-MessageSuccess -message "Migrate successfully initiated for WorkSpaceId $WksId" -title "Successfully Migrated WorkSpace" 926 | } 927 | } 928 | } 929 | }) 930 | 931 | $btnTerminate.Add_Click({ 932 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 933 | $ImpactedList = $lstImpactedWorkSpaces.Items 934 | }else{ 935 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 936 | } 937 | $filteredList = @() 938 | foreach($WS in $ImpactedList){ 939 | $filteredList += $WS 940 | } 941 | $impactCount = $filteredList.Count 942 | $wshell = New-Object -ComObject Wscript.Shell 943 | $response = $wshell.Popup("Are you sure you would like to terminate the selected $impactCount WorkSpaces? This cannot be undone.",0,"Alert",64+4) 944 | if($response -eq 6){ 945 | Write-Logger -message "Executing Terminate on selected WorkSpaceId(s)" 946 | $TermReq = New-Object -TypeName PSobject 947 | $TermReq | Add-Member -NotePropertyName "TermList" -NotePropertyValue $filteredList 948 | $Output = Optimize-APIRequest -requestInfo $TermReq -APICall "Remove-WKSWorkspace" 949 | if($Output.ErrorCode){ 950 | $ErrCode = $Output.ErrorCode.ToString() 951 | $ErrMsg = $Output.ErrorMessage.ToString() 952 | Write-Logger -message "Terminate failed for selected WorkSpaces. Details below:" 953 | Write-Logger -message "Error Code: $ErrCode" 954 | Write-Logger -message "Error Message: $ErrMsg" 955 | Show-MessageError -message "Error Terminating, see log tab for details" -title "Error Terminating WorkSpace" 956 | }else{ 957 | Show-MessageSuccess -message "Terminate API on selected WorkSpaces executed successfully" -title "Successfully Terminated WorkSpace" 958 | Write-Logger -message "Terminate API on selected WorkSpaces executed successfully" 959 | } 960 | } 961 | }) 962 | 963 | $btnEnableMain.Add_Click({ 964 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 965 | foreach($wks in $lstImpactedWorkSpaces.Items){ 966 | $ImpactedList += $wks 967 | } 968 | }else{ 969 | foreach($wks in $lstImpactedWorkSpaces.SelectedItems){ 970 | $ImpactedList += $wks 971 | } 972 | } 973 | $Output = Optimize-APIRequest -requestInfo $ImpactedList -APICall "Enable-Main" 974 | if($Output.ErrorCode){ 975 | $ErrCode = $Output.ErrorCode.ToString() 976 | $ErrMsg = $Output.ErrorMessage.ToString() 977 | Write-Logger -message "Enabling Admin Maintenance failed. Details below:" 978 | Write-Logger -message "Error Code: $ErrCode" 979 | Write-Logger -message "Error Message: $ErrMsg" 980 | Show-MessageError -message "Error enabling Admin Maintenance, see log tab for details" -title "Error Enabling Admin Maintenance" 981 | }else{ 982 | Show-MessageSuccess -message "Terminate API on $WorkSpaceId executed successfully" -title "Successfully Enabled Admin Maintenance" 983 | Write-Logger -message "Enabling Admin Maintenance API ran successfully" 984 | } 985 | }) 986 | 987 | $btnDisableMain.Add_Click({ 988 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 989 | $ImpactedList = $lstImpactedWorkSpaces.Items 990 | }else{ 991 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 992 | } 993 | $Output = Optimize-APIRequest -requestInfo $ImpactedList -APICall "Disable-Main" 994 | if($Output.ErrorCode){ 995 | $ErrCode = $Output.ErrorCode.ToString() 996 | $ErrMsg = $Output.ErrorMessage.ToString() 997 | Write-Logger -message "Terminate failed for WorkSpaceId $WorkSpaceId Details below:" 998 | Write-Logger -message "Error Code: $ErrCode" 999 | Write-Logger -message "Error Message: $ErrMsg" 1000 | Show-MessageError -message "Error Terminating, see log tab for details" -title "Error Terminating WorkSpace" 1001 | }else{ 1002 | Show-MessageSuccess -message "Terminate API on $WorkSpaceId executed successfully" -title "Successfully Terminated WorkSpace" 1003 | } 1004 | }) 1005 | 1006 | $btnUpdateDB.Add_Click({ 1007 | Update-WorkSpaceObject 1008 | Get-ImpactedWS 1009 | }) 1010 | 1011 | $btnExportBulk.Add_Click({ 1012 | if($null -ne $txtReporting.Text){ 1013 | $ImpactedList = $lstImpactedWorkSpaces.Items 1014 | $filteredList = @() 1015 | foreach($WS in $ImpactedList){ 1016 | $filteredList += $WS 1017 | } 1018 | $output = $txtReporting.text + "BulkWorkSpaces.csv" 1019 | try{ 1020 | $filteredList | Export-Csv -Path ($output) -NoTypeInformation 1021 | Show-MessageSuccess -message "Successfully exported CSV to provided path" -title "Successfully Exported" 1022 | }catch{ 1023 | Show-MessageError -message "Export failed to write CSV to provided path." -title "Export Failed" 1024 | } 1025 | }else{ 1026 | Show-MessageError -message "Export failed due to export path not being set on Admin tab." -title "Export Failed" 1027 | } 1028 | }) 1029 | 1030 | #Button to Modify Protocol 1031 | $btnBulkModifyProtocol.Add_Click({ 1032 | if($lstImpactedWorkSpaces.SelectedItems.Count -eq 0){ 1033 | $ImpactedList = $lstImpactedWorkSpaces.Items 1034 | }else{ 1035 | $ImpactedList = $lstImpactedWorkSpaces.SelectedItems 1036 | } 1037 | $BulkProtocolModifyReq = @() 1038 | foreach($WS in $ImpactedList){ 1039 | $BulkProtocolModifyReq += $WS 1040 | } 1041 | $Output = Set-WKSProtocol -ProtocolModifyReq $BulkProtocolModifyReq 1042 | if($Output.ErrorCode){ 1043 | $ErrCode = $Output.ErrorCode 1044 | $ErrMsg = $Output.ErrorMessage 1045 | Write-Logger -message "Protocol modification failed $WorkSpaceId. Details below:" 1046 | Write-Logger -message "Error Code: $ErrCode" 1047 | Write-Logger -message "Error Message: $ErrMsg" 1048 | Show-MessageError -message "Error modifying protocol, see log tab for details" -title "Error Modifying Protocol" 1049 | }else{ 1050 | Show-MessageSuccess -message "Protocol modification on $WorkSpaceId executed successfully" -title "Successfully Modified Protocol" 1051 | Write-Logger -message "Protocol modification on $WorkSpaceId executed successfully" 1052 | } 1053 | }) 1054 | 1055 | ############################################### 1056 | # ! # ! # AppStream GUI # ! # ! # 1057 | ############################################### 1058 | 1059 | # This function creates a object to hold all of your AppStream sessions to act as a local db 1060 | # for the GUI 1061 | function Get-AppStreamSessions(){ 1062 | # Stop new queries, while data is populated 1063 | $global:DataPullInProgress = $true 1064 | $cmboAppStreamHelpDeskUserStateConnectedState.SelectedIndex = 0 1065 | $cmboAppStreamHelpDeskUserState.SelectedIndex = 0 1066 | 1067 | $global:AppStreamDB = @() 1068 | 1069 | # Get session info for target Stack 1070 | $listAppStreamSessions.Items.Clear() 1071 | if($cmboAppStreamHelpDeskRegion.SelectedIndex -ne -1 -and $cmboAppStreamHelpDeskRegion.SelectedIndex -ne 0){ 1072 | if($cmboAppStreamHelpDesk.SelectedIndex -ne -1 -and $cmboAppStreamHelpDesk.SelectedIndex -ne 0){ 1073 | Write-Host "Populating AppStream Session info" 1074 | $region = $cmboAppStreamHelpDeskRegion.SelectedItem.ToString() 1075 | $stack = $global:TotalStacks | Where-Object { ($_[0].arn -split ":")[3] -eq $region } | Where-Object { $_[0].Name -eq $cmboAppStreamHelpDesk.SelectedItem.ToString() } 1076 | if($null -ne $stack.AssocFleet){ 1077 | $SessionList = Import-AppStreamSessions -stackName $stack.Name -fleetName $stack.AssocFleet -region $region -throttleControl $true 1078 | foreach ($session in $SessionList){ 1079 | $AS2Session = New-Object -TypeName PSobject 1080 | $AS2Session | Add-Member -NotePropertyName "UserId" -NotePropertyValue $session.UserId 1081 | $AS2Session | Add-Member -NotePropertyName "Stack" -NotePropertyValue $stack.Name 1082 | $AS2Session | Add-Member -NotePropertyName "State" -NotePropertyValue $session.State 1083 | $AS2Session | Add-Member -NotePropertyName "ConnectedState" -NotePropertyValue $session.ConnectionState 1084 | $AS2Session | Add-Member -NotePropertyName "StartTime" -NotePropertyValue $session.StartTime.ToString() 1085 | $AS2Session | Add-Member -NotePropertyName "PrivateIP" -NotePropertyValue $session.NetworkAccessConfiguration.EniPrivateIpAddress 1086 | $AS2Session | Add-Member -NotePropertyName "Id" -NotePropertyValue $session.Id 1087 | $global:AppStreamDB += $AS2Session 1088 | } 1089 | $fleetDetails = Get-APSFleetList -Name $stack.AssocFleet -Region $region 1090 | $lblFleetName.Content = $fleetDetails.Name 1091 | $lblFleetState.Content = $fleetDetails.State 1092 | $lblFleetMode.Content = $fleetDetails.FleetType 1093 | $lblFleetImage.Content = $fleetDetails.ImageName 1094 | $lblFleetInstances.Content = $fleetDetails.ComputeCapacityStatus.Running 1095 | $lblFleetAvailable.Content = $fleetDetails.ComputeCapacityStatus.Available 1096 | $lblFleetInUse.Content = $fleetDetails.ComputeCapacityStatus.InUse 1097 | $lblFleetUserSessions.Content = $fleetDetails.ComputeCapacityStatus.ActualUserSessions 1098 | $lblFleetActiveSessions.Content = $fleetDetails.ComputeCapacityStatus.ActiveUserSessions 1099 | $lblFleetAvailableSessions.Content = $fleetDetails.ComputeCapacityStatus.AvailableUserSessions 1100 | if($null -eq $fleetDetails.FleetErrors.ErrorCode){ 1101 | $txtboxFleetErrors.Text = "N/A" 1102 | }else{ 1103 | $txtboxFleetErrors.Text = $fleetDetails.FleetErrors.ErrorMessage 1104 | } 1105 | } 1106 | } 1107 | } 1108 | $global:DataPullInProgress=$false 1109 | Search-AppStreamSession 1110 | } 1111 | 1112 | # This functions filters your AppStream db object to only display information matching your search criteria 1113 | function Search-AppStreamSession(){ 1114 | $listAppStreamSessions.Items.Clear() 1115 | 1116 | $filtered = $global:AppStreamDB 1117 | if($txtAppStreamHelpDeskUserId.Text -ne ""){ 1118 | $filtered = $filtered | Where-Object { ($_.UserId -like ($txtAppStreamHelpDeskUserId.Text + "*")) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1119 | } 1120 | if($txtAppStreamHelpDeskUserSessionId.Text -ne ""){ 1121 | $filtered = $filtered | Where-Object { ($_.SessionId -like ($txtAppStreamHelpDeskUserSessionId.Text + "*")) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1122 | } 1123 | if($txtAppStreamHelpDeskUserIP.Text -ne ""){ 1124 | $filtered = $filtered | Where-Object { ($_.PrivateIP -like ($txtAppStreamHelpDeskUserIP.Text + "*")) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1125 | } 1126 | if($cmboAppStreamHelpDesk.SelectedIndex -ne -1 -and $cmboAppStreamHelpDesk.SelectedIndex -ne 0){ 1127 | $filtered = $filtered | Where-Object { ($_.Stack -like ($cmboAppStreamHelpDesk.SelectedItem.ToString())) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1128 | } 1129 | if($cmboAppStreamHelpDeskUserState.SelectedItem.ToString() -ne "All"){ 1130 | $filtered = $filtered | Where-Object { ($_.State -like ($cmboAppStreamHelpDeskUserState.SelectedItem.ToString())) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1131 | } 1132 | if($cmboAppStreamHelpDeskUserStateConnectedState.SelectedItem.ToString() -ne "All"){ 1133 | $filtered = $filtered | Where-Object { ($_.ConnectedState -like ($cmboAppStreamHelpDeskUserStateConnectedState.SelectedItem.ToString())) } | Select-Object UserId, Stack, State, ConnectedState, StartTime, PrivateIP, Id 1134 | } 1135 | 1136 | foreach($session in $filtered){ 1137 | $listAppStreamSessions.items.Add($session) 1138 | } 1139 | } 1140 | 1141 | # This function pulls all regions that you have deployed AppStream 1142 | function Get-AppStreamRegions(){ 1143 | $cmboAppStreamHelpDeskRegion.items.clear() 1144 | $cmboAppStreamHelpDeskRegion.items.add("Select a Region") | Out-Null 1145 | $cmboAppStreamHelpDesk.items.clear() 1146 | $cmboAppStreamHelpDesk.items.add("Pending Region") | Out-Null 1147 | $listPoolsSessions.items.clear() 1148 | $global:TotalStacks = @() 1149 | $tempStacks = Import-AppStreamRegions -throttleControl $true 1150 | foreach ($stack in $tempStacks){ 1151 | $parsedRegion = ($stack.arn -split ":")[3] 1152 | if ($cmboAppStreamHelpDeskRegion.Items -notcontains $parsedRegion){ 1153 | $cmboAppStreamHelpDeskRegion.items.add("$parsedRegion") | Out-Null 1154 | } 1155 | $fleetName = $null 1156 | $fleetName = Get-APSAssociatedFleetList -StackName $stack.Name -Region $parsedRegion 1157 | $stack | Add-Member -NotePropertyName "AssocFleet" -NotePropertyValue $fleetName 1158 | $global:TotalStacks += $stack 1159 | } 1160 | 1161 | $cmboAppStreamHelpDeskRegion.SelectedIndex=0 1162 | } 1163 | 1164 | # This section's AppStream actions correspond with a GUI button action. The button objects below are 1165 | # created from objects outlined within the XML. 1166 | 1167 | $btnAppStreamHelpDeskSessionDisconnect.Add_Click({ 1168 | $sessionId=$listAppStreamSessions.SelectedItems.Id 1169 | Revoke-APSSession -SessionId $sessionId 1170 | Start-Sleep -Seconds 2 1171 | Get-AppStreamSessions 1172 | }) 1173 | 1174 | $btnAppStreamHelpDeskRemoteAssist.Add_Click({ 1175 | $privateIP=$listAppStreamSessions.SelectedItems.PrivateIP 1176 | $parameters="/offerRA $privateIP" 1177 | $exe="msra.exe" 1178 | start-process $exe $parameters -Wait 1179 | }) 1180 | 1181 | $txtAppStreamHelpDeskUserId.Add_TextChanged({ 1182 | Search-AppStreamSession 1183 | }) 1184 | $txtAppStreamHelpDeskUserSessionId.Add_TextChanged({ 1185 | Search-AppStreamSession 1186 | }) 1187 | $txtAppStreamHelpDeskUserIP.Add_TextChanged({ 1188 | Search-AppStreamSession 1189 | }) 1190 | 1191 | $cmboAppStreamHelpDesk.add_SelectionChanged({ 1192 | if(!$global:DataPullInProgress -and $cmboAppStreamHelpDesk.SelectedIndex -ne 0){ 1193 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 1194 | $lblLastASDBUpdate.Content = $date 1195 | Get-AppStreamSessions 1196 | } 1197 | }) 1198 | 1199 | $cmboAppStreamHelpDeskRegion.add_SelectionChanged({ 1200 | $lblFleetName.Content = "" 1201 | $lblFleetState.Content = "" 1202 | $lblFleetMode.Content = "" 1203 | $lblFleetImage.Content = "" 1204 | $txtboxFleetErrors.Text = "" 1205 | $lblFleetInstances.Content = "0" 1206 | $lblFleetAvailable.Content = "0" 1207 | $lblFleetInUse.Content = "0" 1208 | $lblFleetUserSessions.Content = "0" 1209 | $lblFleetActiveSessions.Content = "0" 1210 | $lblFleetAvailableSessions.Content = "0" 1211 | if($cmboAppStreamHelpDeskRegion.SelectedIndex -ne -1 -and $cmboAppStreamHelpDeskRegion.SelectedIndex -ne 0){ 1212 | $global:DataPullInProgress = $false 1213 | $cmboAppStreamHelpDesk.items.clear() 1214 | $cmboAppStreamHelpDesk.items.add("Select a Stack") | Out-Null 1215 | $region = $cmboAppStreamHelpDeskRegion.SelectedItem.ToString() 1216 | $filteredStacks = $global:TotalStacks | Where-Object { ($_[0].arn -split ":")[3] -eq $region } 1217 | foreach($stack in $filteredStacks){ 1218 | $tmpStack = $stack.Name 1219 | $cmboAppStreamHelpDesk.items.add("$tmpStack") | Out-Null 1220 | } 1221 | $cmboAppStreamHelpDesk.SelectedIndex = 0 1222 | }else{ 1223 | $cmboAppStreamHelpDesk.SelectedIndex = 0 1224 | } 1225 | }) 1226 | 1227 | $cmboAppStreamHelpDeskUserState.add_SelectionChanged({ 1228 | if(!$global:DataPullInProgress){ 1229 | Search-AppStreamSession 1230 | } 1231 | }) 1232 | $cmboAppStreamHelpDeskUserStateConnectedState.add_SelectionChanged({ 1233 | if(!$global:DataPullInProgress){ 1234 | Search-AppStreamSession 1235 | } 1236 | }) 1237 | 1238 | $btnAppStreamHelpDeskSessionExport.Add_Click({ 1239 | if($null -ne $txtReporting.Text){ 1240 | $ImpactedList = $listAppStreamSessions.Items 1241 | $filteredList = @() 1242 | foreach($AS in $ImpactedList){ 1243 | $filteredList += $AS 1244 | } 1245 | $output = $txtReporting.text + "AppStreamSessions.csv" 1246 | try{ 1247 | $filteredList | Export-Csv -Path ($output) -NoTypeInformation 1248 | Show-MessageSuccess -message "Successfully exported CSV to provided path" -title "Successfully Exported" 1249 | }catch{ 1250 | Show-MessageError -message "Export failed to write CSV to provided path." -title "Export Failed" 1251 | } 1252 | }else{ 1253 | Show-MessageError -message "Export failed due to export path not being set on Admin tab." -title "Export Failed" 1254 | } 1255 | }) 1256 | 1257 | $btnUpdateASData.Add_Click({ 1258 | if(!$global:DataPullInProgress -and $cmboAppStreamHelpDesk.SelectedIndex -ne 0){ 1259 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 1260 | $lblLastASDBUpdate.Content = $date 1261 | Get-AppStreamSessions 1262 | } 1263 | }) 1264 | 1265 | ############################################### 1266 | # ! # ! # WorkSpaces Pools GUI # ! # ! # 1267 | ############################################### 1268 | 1269 | # This function pulls all regions that you have deployed WorkSpaces Pools 1270 | function Get-WksPoolsRegions(){ 1271 | $cmboPoolsRegion.items.clear() 1272 | $cmboPoolsRegion.items.add("Select a Region") | Out-Null 1273 | $cmboPoolsSelect.items.clear() 1274 | $cmboPoolsSelect.items.add("Pending Region") | Out-Null 1275 | $global:WksPoolsDirectories = @() 1276 | $global:WksPools = @() 1277 | $global:WksPoolsDirectories = Get-WksPoolsDirectories -throttleControl $true 1278 | $tempRegions = $global:WksPoolsDirectories | Select-Object Region -Unique 1279 | $tempPools = Get-WksPools -DeployedRegions $tempRegions -bundles $global:WorkSpacesBundles -throttleControl $true 1280 | foreach ($pool in $tempPools){ 1281 | $parsedRegion = ($pool.PoolArn -split ":")[3] 1282 | if ($cmboPoolsRegion.Items -notcontains $parsedRegion){ 1283 | $cmboPoolsRegion.items.add("$parsedRegion") | Out-Null 1284 | } 1285 | $global:WksPools += $pool 1286 | } 1287 | 1288 | $cmboPoolsRegion.SelectedIndex=0 1289 | } 1290 | 1291 | function Get-PoolsSessions(){ 1292 | # Stop new queries, while data is populated 1293 | $global:PoolsDataPullInProgress = $true 1294 | $cmboPoolsUserStateConnectedState.SelectedIndex = 0 1295 | $global:WksPoolsSessions = @() 1296 | 1297 | # Get session info for target Stack 1298 | $listPoolsSessions.Items.Clear() 1299 | if($cmboPoolsRegion.SelectedIndex -ne -1 -and $cmboPoolsRegion.SelectedIndex -ne 0){ 1300 | if($cmboPoolsSelect.SelectedIndex -ne -1 -and $cmboPoolsSelect.SelectedIndex -ne 0){ 1301 | $region = $cmboPoolsRegion.SelectedItem.ToString() 1302 | $pool = $global:WksPools | Where-Object { ($_[0].PoolArn -split ":")[3] -eq $region } | Where-Object { $_[0].PoolName -eq $cmboPoolsSelect.SelectedItem.ToString() } 1303 | if($null -ne $pool){ 1304 | $SessionList = Import-WksPoolsSessions -poolId $pool.PoolId -region $region -throttleControl $true 1305 | foreach ($session in $SessionList){ 1306 | $PoolsSession = New-Object -TypeName PSobject 1307 | $PoolsSession | Add-Member -NotePropertyName "UserId" -NotePropertyValue $session.UserId 1308 | $PoolsSession | Add-Member -NotePropertyName "ConnectedState" -NotePropertyValue $session.ConnectionState 1309 | $PoolsSession | Add-Member -NotePropertyName "StartTime" -NotePropertyValue $session.StartTime.ToString() 1310 | $PoolsSession | Add-Member -NotePropertyName "PrivateIP" -NotePropertyValue $session.NetworkAccessConfiguration.EniPrivateIpAddress 1311 | $PoolsSession | Add-Member -NotePropertyName "SessionId" -NotePropertyValue $session.SessionId 1312 | $global:WksPoolsSessions += $PoolsSession 1313 | } 1314 | $lblPoolName.Content = $pool.PoolName 1315 | $lblPoolState.Content = $pool.State 1316 | $lblPoolId.Content = $pool.PoolId 1317 | $lblPoolsBundle.Content = $pool.BundleId 1318 | $lblPoolsUserSessions.Content = $pool.CapacityStatus.ActualUserSessions 1319 | $lblPoolsActiveSessions.Content = $pool.CapacityStatus.ActiveUserSessions 1320 | $lblPoolsAvailableSessions.Content = $pool.CapacityStatus.AvailableUserSessions 1321 | $lblPoolsDesiredSessions.Content = $pool.CapacityStatus.DesiredUserSessions 1322 | if($null -eq $fleetDetails.Errors.ErrorCode){ 1323 | $txtboxPoolsErrors.Text = "N/A" 1324 | }else{ 1325 | $txtboxPoolsErrors.Text = $fleetDetails.Errors.ErrorMessage 1326 | } 1327 | } 1328 | } 1329 | } 1330 | $global:PoolsDataPullInProgress=$false 1331 | Search-WksPoolsSessions 1332 | } 1333 | 1334 | # This functions filters your AppStream db object to only display information matching your search criteria 1335 | function Search-WksPoolsSessions(){ 1336 | $listPoolsSessions.Items.Clear() 1337 | 1338 | $filtered = $global:WksPoolsSessions 1339 | if($txtPoolsUserId.Text -ne ""){ 1340 | $filtered = $filtered | Where-Object { ($_.UserId -like ($txtPoolsUserId.Text + "*")) } | Select-Object UserId, ConnectedState, StartTime, PrivateIP, SessionId 1341 | } 1342 | if($txtPoolsUserSessionId.Text -ne ""){ 1343 | $filtered = $filtered | Where-Object { ($_.SessionId -like ($txtPoolsUserSessionId.Text + "*")) } | Select-Object UserId, ConnectedState, StartTime, PrivateIP, SessionId 1344 | } 1345 | if($txtPoolsUserIP.Text -ne ""){ 1346 | $filtered = $filtered | Where-Object { ($_.PrivateIP -like ($txtPoolsUserIP.Text + "*")) } | Select-Object UserId, ConnectedState, StartTime, PrivateIP, SessionId 1347 | } 1348 | if($cmboPoolsUserStateConnectedState.SelectedItem.ToString() -ne "All"){ 1349 | $filtered = $filtered | Where-Object { ($_.ConnectedState -like ($cmboPoolsUserStateConnectedState.SelectedItem.ToString())) } | Select-Object UserId, ConnectedState, StartTime, PrivateIP, SessionId 1350 | } 1351 | 1352 | foreach($session in $filtered){ 1353 | $listPoolsSessions.items.Add($session) 1354 | } 1355 | } 1356 | 1357 | $cmboPoolsRegion.add_SelectionChanged({ 1358 | $lblPoolName.Content = "" 1359 | $lblPoolState.Content = "" 1360 | $lblPoolId.Content = "" 1361 | $lblPoolsBundle.Content = "" 1362 | $txtboxPoolsErrors.Text = "" 1363 | $lblPoolsDesiredSessions = "0" 1364 | $lblPoolsUserSessions.Content = "0" 1365 | $lblPoolsActiveSessions.Content = "0" 1366 | $lblPoolsAvailableSessions.Content = "0" 1367 | if($cmboPoolsRegion.SelectedIndex -ne -1 -and $cmboPoolsRegion.SelectedIndex -ne 0){ 1368 | $global:PoolsDataPullInProgress = $false 1369 | $cmboPoolsSelect.items.clear() 1370 | $cmboPoolsSelect.items.add("Select a Pool") | Out-Null 1371 | $region = $cmboPoolsRegion.SelectedItem.ToString() 1372 | $filteredPools = $global:WksPools | Where-Object { ($_[0].PoolArn -split ":")[3] -eq $region } 1373 | foreach($pool in $filteredPools){ 1374 | $tmpPool = $pool.PoolName 1375 | $cmboPoolsSelect.items.add("$tmpPool") | Out-Null 1376 | } 1377 | $cmboPoolsSelect.SelectedIndex = 0 1378 | }else{ 1379 | $cmboPoolsSelect.SelectedIndex = 0 1380 | } 1381 | }) 1382 | 1383 | $cmboPoolsSelect.add_SelectionChanged({ 1384 | if(!$global:PoolsDataPullInProgress -and $cmboPoolsSelect.SelectedIndex -ne 0){ 1385 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 1386 | $lblLastPoolsDBUpdate.Content = $date 1387 | Get-PoolsSessions 1388 | }else{ 1389 | $lblLastPoolsDBUpdate.Content = "Pending" 1390 | } 1391 | }) 1392 | 1393 | $cmboPoolsUserStateConnectedState.add_SelectionChanged({ 1394 | if(!$global:PoolsDataPullInProgress){ 1395 | Search-WksPoolsSessions 1396 | } 1397 | }) 1398 | 1399 | $txtPoolsUserId.Add_TextChanged({ 1400 | if(!$global:PoolsDataPullInProgress){ 1401 | Search-WksPoolsSessions 1402 | } 1403 | }) 1404 | 1405 | $txtPoolsUserSessionId.Add_TextChanged({ 1406 | if(!$global:PoolsDataPullInProgress){ 1407 | Search-WksPoolsSessions 1408 | } 1409 | }) 1410 | 1411 | $txtPoolsUserIP.Add_TextChanged({ 1412 | if(!$global:PoolsDataPullInProgress){ 1413 | Search-WksPoolsSessions 1414 | } 1415 | }) 1416 | 1417 | $btnPoolsSessionExport.Add_Click({ 1418 | if($null -ne $txtReporting.Text){ 1419 | $ImpactedList = $listPoolsSessions.Items 1420 | $filteredList = @() 1421 | foreach($poolSess in $ImpactedList){ 1422 | $filteredList += $poolSess 1423 | } 1424 | $output = $txtReporting.text + "WksPoolsSessions.csv" 1425 | try{ 1426 | $filteredList | Export-Csv -Path ($output) -NoTypeInformation 1427 | Show-MessageSuccess -message "Successfully exported CSV to provided path" -title "Successfully Exported" 1428 | }catch{ 1429 | Show-MessageError -message "Export failed to write CSV to provided path." -title "Export Failed" 1430 | } 1431 | }else{ 1432 | Show-MessageError -message "Export failed due to export path not being set on Admin tab." -title "Export Failed" 1433 | } 1434 | }) 1435 | 1436 | $btnPoolsSessionDisconnect.Add_Click({ 1437 | $sessionId = $listPoolsSessions.SelectedItems.SessionId 1438 | Remove-WKSWorkspacesPoolSession -SessionId $sessionId 1439 | Start-Sleep -Seconds 2 1440 | if(!$global:PoolsDataPullInProgress){ 1441 | Get-PoolsSessions 1442 | } 1443 | }) 1444 | 1445 | $btnPoolsRemoteAssist.Add_Click({ 1446 | $privateIP = $listPoolsSessions.SelectedItems.PrivateIP 1447 | $parameters = "/offerRA $privateIP" 1448 | $exe = "msra.exe" 1449 | start-process $exe $parameters -Wait 1450 | }) 1451 | 1452 | $btnUpdatePoolsData.Add_Click({ 1453 | if(!$global:PoolsDataPullInProgress -and $cmboAppStreamHelpDesk.SelectedIndex -ne 0){ 1454 | $date = (get-date -Format "MM/dd/yyyy HH:mm") | Out-String 1455 | $lblLastPoolsDBUpdate.Content = $date 1456 | Get-PoolsSessions 1457 | } 1458 | }) 1459 | 1460 | ############################################### 1461 | # ! # ! # WorkSpaces Admin GUI Actions # ! # ! # 1462 | ############################################### 1463 | 1464 | $btnQueryWSUpdateDB.Add_Click({ 1465 | Update-WorkSpaceObject 1466 | }) 1467 | 1468 | $cmboAdminSelectRegionValue.add_SelectionChanged({ 1469 | if(($cmboAdminSelectRegionValue.SelectedValue) -ne 'Select Region'){ 1470 | Update-WorkSpaceAdminDirectories 1471 | Update-ServiceQuotas 1472 | } 1473 | }) 1474 | 1475 | $cmboAdminWSDirectory.add_SelectionChanged({ 1476 | if(($cmboDeploymentWSDirectory.SelectedValue) -ne 'Select a Directory'){ 1477 | Update-WorkSpaceAdminDirectoryDetails 1478 | } 1479 | }) 1480 | 1481 | #Function loads all of the directories in a region. 1482 | function Update-WorkSpaceAdminDirectories(){ 1483 | $cmboAdminWSDirectory.Items.Clear() 1484 | $cmboAdminWSDirectory.Items.Add("Select a Directory") 1485 | $Directories = $global:WorkSpacesDirectoryDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedItem)} | Select-Object directoryId, Region -Unique 1486 | foreach ($Directory in $Directories ){ 1487 | $directorySTR = $Directory.directoryId 1488 | $cmboAdminWSDirectory.Items.Add($directorySTR) 1489 | } 1490 | $cmboAdminWSDirectory.SelectedIndex=0 1491 | } 1492 | 1493 | function Update-WorkSpaceAdminDirectoryDetails(){ 1494 | $directoryInfo = $global:WorkSpacesDirectoryDB | Where-Object { ($_.directoryId -eq $cmboAdminWSDirectory.SelectedValue)} | Get-Unique 1495 | $lblAdminDirectoryAliasContent.Content = $directoryInfo.Alias 1496 | $lblAdminDirectoryNameContent.Content = $directoryInfo.DirectoryName 1497 | $lblAdminDirectoryIdContent.Content = $directoryInfo.directoryId 1498 | $lblAdminDirectoryRegCodeContent.Content = $directoryInfo.RegistrationCode 1499 | $lblAdminDirectoryStateContent.Content = $directoryInfo.State 1500 | $lblAdminDirectoryLocalAdinContent.Content = $directoryInfo.WorkspaceCreationProperties.UserEnabledAsLocalAdministrator 1501 | $lblAdminDirectoryTenancyContent.Content = $directoryInfo.Tenancy 1502 | $lblAdminDirectoryIPContent.Content = $directoryInfo.directoryAvailableIPs 1503 | } 1504 | 1505 | function Update-ServiceQuotas(){ 1506 | $targetDirectory = $global:WorkSpacesServiceQuotaDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Get-Unique 1507 | 1508 | # Total WorkSpaces Quota 1509 | $lblAdminWSServiceQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)}).count) 1510 | $lblAdminWSServiceQuotaCurrentMax.Content = $targetDirectory.quotaWks 1511 | 1512 | # Total WorkSpaces 4xl Quota 1513 | $lblAdminWSService4xlQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object {($_.WorkspaceProperties.ComputeTypeName).ToUpper -eq "GENERALPURPOSE_4XLARGE"}).count) 1514 | $lblAdminWSService4xlQuotaCurrentMax.Content = $targetDirectory.quotaWks4xl 1515 | 1516 | # Total WorkSpaces 8xl Quota 1517 | $lblAdminWSService8xlQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object {($_.WorkspaceProperties.ComputeTypeName).ToUpper -eq "GENERALPURPOSE_8XLARGE"}).count) 1518 | $lblAdminWSService8xlQuotaCurrentMax.Content = $targetDirectory.quotaWks8xl 1519 | 1520 | # Total WorkSpaces for StandBy 1521 | $lblAdminWSStandByServiceQuotaCurrent.Content = ($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object { -not ([string]::IsNullOrEmpty($_.StandbyWorkspacesProperties)) }).count 1522 | $lblAdmintWSStandByServiceQuotaMax.Content = $targetDirectory.quotaStandby 1523 | 1524 | # Total WorkSpaces for GraphicsPro 1525 | $lblAdminWSGraphicsProServiceQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object {($_.WorkspaceProperties.ComputeTypeName).ToUpper -eq "GRAPHICSPRO"}).count) 1526 | $lblAdminWSGraphicsProServiceQuotaMax.Content = $targetDirectory.quotaGraphicsPro 1527 | 1528 | # Total WorkSpaces for Graphics G4dn 1529 | $lblAdminWSGraphicsg4dnServiceQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object {($_.WorkspaceProperties.ComputeTypeName).ToUpper -eq "GRAPHICS.G4DN"}).count) 1530 | $lblAdminWSGraphicsg4dnProServiceQuotaMax.Content = $targetDirectory.quotaG4dn 1531 | 1532 | # Total WorkSpaces for GraphicsPro G4dn 1533 | $lblAdminWSGraphicsg4dnProServiceQuotaCurrent.Content = (($global:WorkSpacesDB | Where-Object { ($_.Region -eq $cmboAdminSelectRegionValue.SelectedValue)} | Where-Object {($_.WorkspaceProperties.ComputeTypeName).ToUpper -eq "GRAPHICSPRO.G4DN"}).count) 1534 | $lblAdminWSGraphicsg4dnServiceQuotaMax.Content = $targetDirectory.quotaG4dnPro 1535 | 1536 | } 1537 | 1538 | ############################################### 1539 | # ! # ! # EUC Toolkit Initialization # ! # ! # 1540 | ############################################### 1541 | 1542 | # WorkSpaces 1543 | $cmboProtocol.items.Add("All") | Out-Null 1544 | $cmboProtocol.items.Add("BYOP") | Out-Null 1545 | $cmboProtocol.items.Add("DCV") | Out-Null 1546 | $cmboProtocol.items.Add("PCoIP") | Out-Null 1547 | $cmboProtocol.SelectedIndex=0 1548 | $cmboBulkProtocol.items.Add("All") | Out-Null 1549 | $cmboBulkProtocol.items.Add("BYOP") | Out-Null 1550 | $cmboBulkProtocol.items.Add("DCV") | Out-Null 1551 | $cmboBulkProtocol.items.Add("PCoIP") | Out-Null 1552 | $cmboBulkProtocol.SelectedIndex=0 1553 | $selectRunningModeFilterCombo.items.Add("Select Running Mode") | Out-Null 1554 | $selectRunningModeFilterCombo.items.Add("ALWAYS_ON") | Out-Null 1555 | $selectRunningModeFilterCombo.items.Add("AUTO_STOP") | Out-Null 1556 | $selectRunningModeFilterCombo.SelectedIndex=0 1557 | $migrateBundleCombo.items.add("Select Bundle") | Out-Null 1558 | $migrateBundleCombo.SelectedIndex=0 1559 | 1560 | Update-WorkSpaceObject 1561 | Search-WorkSpaces 1562 | $regions = $global:WorkSpacesDB.Region | Select-Object -Unique 1563 | foreach($region in $regions){ 1564 | if(-not(($selectWKSRegion.Items).Contains($region))){ 1565 | $selectWKSRegion.Items.Add($region) | Out-Null 1566 | $cmboAdminSelectRegionValue.Items.Add($region) | Out-Null 1567 | } 1568 | } 1569 | $global:cloudWatchRun=0 1570 | $cmboAdminSelectRegionValue.SelectedIndex=0 1571 | 1572 | # Pools 1573 | write-host "Populating WorkSpaces Pools info" 1574 | $cmboPoolsUserStateConnectedState.items.add("All") | Out-Null 1575 | $cmboPoolsUserStateConnectedState.items.add("CONNECTED") | Out-Null 1576 | $cmboPoolsUserStateConnectedState.items.add("NOT_CONNECTED") | Out-Null 1577 | $cmboPoolsUserStateConnectedState.SelectedIndex = 0 1578 | Get-WksPoolsRegions 1579 | 1580 | # AppStream 1581 | $global:DataPullInProgress=$true 1582 | $cmboAppStreamHelpDesk.items.add("Select a Stack") | Out-Null 1583 | $cmboAppStreamHelpDesk.SelectedIndex = 0 1584 | $cmboAppStreamHelpDeskUserStateConnectedState.items.add("All") | Out-Null 1585 | $cmboAppStreamHelpDeskUserStateConnectedState.items.add("CONNECTED") | Out-Null 1586 | $cmboAppStreamHelpDeskUserStateConnectedState.items.add("NOT_CONNECTED") | Out-Null 1587 | $cmboAppStreamHelpDeskUserStateConnectedState.SelectedIndex = 0 1588 | 1589 | $cmboAppStreamHelpDeskUserState.items.Clear() 1590 | $cmboAppStreamHelpDeskUserState.items.add("All") | Out-Null 1591 | $cmboAppStreamHelpDeskUserState.items.add("ACTIVE") | Out-Null 1592 | $cmboAppStreamHelpDeskUserState.SelectedIndex = 0 1593 | $global:DataPullInProgress=$false 1594 | Get-AppStreamRegions 1595 | 1596 | 1597 | # Preset admin tab textboxes 1598 | $tmpPath = $($PSScriptRoot)+"\Assets\" 1599 | 1600 | # If there is a settings CSV with Values 1601 | $settingsCSVFile = $tmpPath+"Settings.csv" 1602 | if ([System.IO.File]::Exists($settingsCSVFile)) { 1603 | $loadSettings = Import-Csv $settingsCSVFile 1604 | $loadSettings | ForEach{ 1605 | $txtServerSideLogs.Text = $_.WorkSpaceSideLogs 1606 | $txtBackUpDest.Text = $_.Backups 1607 | $txtPSExecPath.Text = $_.PSEXEC 1608 | $txtDisk2VHDPath.Text = $_.Disk2VHD 1609 | $txtReporting.Text = $_.Reporting 1610 | $txtCloudWatchAccessLogs.Text= $_.CloudWatchAccessLogs 1611 | } 1612 | } else { 1613 | $txtServerSideLogs.Text = $tmpPath 1614 | $txtBackUpDest.Text = $tmpPath 1615 | $txtPSExecPath.Text = $tmpPath 1616 | $txtDisk2VHDPath.Text = $tmpPath 1617 | $txtReporting.Text = $tmpPath 1618 | } 1619 | 1620 | # Set index on Main items 1621 | $SearchResults.SelectedIndex=0 1622 | 1623 | # Set index on Region Bulk selection 1624 | $selectWKSRegion.SelectedIndex=0 1625 | 1626 | Write-Logger -message "EUC Toolkit Launched" 1627 | write-host "Powershell GUI Loaded" 1628 | 1629 | #Show Form 1630 | $WksMainForm.ShowDialog() | out-null --------------------------------------------------------------------------------