├── CONTRIBUTING.md
├── Director
├── DirectorReconfigureWithLogonMod.ps1
└── README.md
├── PVS
├── LICENSE.MD
├── README.MD
├── README.md
└── vDiskReplicator_v2.ps1
├── README.md
├── Storefront
├── Create-Storefront3-Store.ps1
└── README.md
├── XAXD
├── AutoscaleMcs
│ ├── Invoke-AutoscaleMachineCreation.ps1
│ ├── LICENSE.MD
│ └── README.MD
├── SiteFeatureDetection
│ ├── README.MD
│ └── XDFeatureDetection.ps1
└── VMsInUnknownPowerState
│ ├── Get-VmInUnknownPowerState.ps1
│ ├── LICENSE.MD
│ └── README.MD
└── config
└── allscripts.json
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Each script(s) should be in its own sub-directory under the relevant product directory and contain a README.md, LICENSE.MD and script files. For example
11 | ```
12 | Storefront
13 | Create Store
14 | CreateNewStore.ps1
15 | README.md
16 | LICENSE.md
17 | ```
18 | 2. Comment changes to scripts in the appropiate location within the affected script.
19 | 3. Document any external depencies that scripts would need to execute sucessfully.
20 | 4. Update the README.md with details of changes if changes affect input, output parameters, file locations, help and if scripts using containers, container parameters.
21 | 5. Increase the version numbers in any affected script and the README.md to the new version that this
22 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
23 |
24 | ## Code of Conduct
25 |
26 | ### Our Pledge
27 |
28 | In the interest of fostering an open and welcoming environment, we as
29 | contributors and maintainers pledge to making participation in our project and
30 | our community a harassment-free experience for everyone, regardless of age, body
31 | size, disability, ethnicity, gender identity and expression, level of experience,
32 | nationality, personal appearance, race, religion, or sexual identity and
33 | orientation.
34 |
35 | ### Our Standards
36 |
37 | Examples of behavior that contributes to creating a positive environment
38 | include:
39 |
40 | * Using welcoming and inclusive language
41 | * Being respectful of differing viewpoints and experiences
42 | * Gracefully accepting constructive criticism
43 | * Focusing on what is best for the community
44 | * Showing empathy towards other community members
45 |
46 | Examples of unacceptable behavior by participants include:
47 |
48 | * The use of sexualized language or imagery and unwelcome sexual attention or
49 | advances
50 | * Trolling, insulting/derogatory comments, and personal or political attacks
51 | * Public or private harassment
52 | * Publishing others' private information, such as a physical or electronic
53 | address, without explicit permission
54 | * Other conduct which could reasonably be considered inappropriate in a
55 | professional setting
56 |
57 | ### Our Responsibilities
58 |
59 | Project maintainers are responsible for clarifying the standards of acceptable
60 | behavior and are expected to take appropriate and fair corrective action in
61 | response to any instances of unacceptable behavior.
62 |
63 | Project maintainers have the right and responsibility to remove, edit, or
64 | reject comments, commits, code, wiki edits, issues, and other contributions
65 | that are not aligned to this Code of Conduct, or to ban temporarily or
66 | permanently any contributor for other behaviors that they deem inappropriate,
67 | threatening, offensive, or harmful.
68 |
69 | ### Scope
70 |
71 | This Code of Conduct applies both within project spaces and in public spaces
72 | when an individual is representing the project or its community. Examples of
73 | representing a project or community include using an official project e-mail
74 | address, posting via an official social media account, or acting as an appointed
75 | representative at an online or offline event. Representation of a project may be
76 | further defined and clarified by project maintainers.
77 |
78 | ### Attribution
79 |
80 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
81 | available at [http://contributor-covenant.org/version/1/4][version]
82 |
83 | [homepage]: http://contributor-covenant.org
84 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/Director/DirectorReconfigureWithLogonMod.ps1:
--------------------------------------------------------------------------------
1 | # /*************************************************************************
2 | # *
3 | # * THIS SAMPLE CODE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
4 | # * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5 | # * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
6 | # *
7 | # *************************************************************************/
8 | <#
9 | Script to configure director after an upgrade or just to make sure that all servers are
10 | configured the same. Also a great way to document the settings you want in your environment.
11 |
12 | script also modifies the Logon.aspx file to pre-populate the domain field and
13 | show the windows server name on the logon page (for when you loadbalance the servers).
14 |
15 | When run the script will copy the current configuration to a local folder for safekeeping.
16 | Files are only written to the server if changes were made.
17 | #>
18 |
19 |
20 | #Add configuration entires from the application config into the hashtable.
21 | #Set the value to "delete" if you want it to be removed completely.
22 |
23 | $AppKeys = @{}
24 | $AppKeys["Service.AutoDiscoveryAddresses"] = "Farm1Controller,Farm2Controller,Farm3Conroller"
25 | $AppKeys["UI.EnableSslCheck"] = "false"
26 | $AppKeys["Service.AutoDiscoveryAddressesXA"] = "XAFarm1ZDC,XAFarm2ZDC"
27 |
28 | #This will remove the ApplicationSetting completely from the Director configuration:
29 | $AppKeys["Service.AutoDiscoveryAddressesXA"] = "delete"
30 |
31 | #List all director servers to be configured
32 | $directorServers = "Director1",
33 | "Director2",
34 | "Director3"
35 |
36 |
37 | #Domain name to be shown in the domain field of the logon page.
38 | #Does not support changing an existing value
39 | $DomainName = "YOURDOMAIN"
40 |
41 | #location where files will be copied before making any changes. Files are marked with source server and time stamp.
42 | $LocalBackupFolder = "c:\temp"
43 |
44 | $scriptTime = Get-Date
45 | foreach ($server in $directorServers) {
46 | write-host $server
47 | #copy the current config files to some place local
48 | $webConfig = "\\$server\c$\inetpub\wwwroot\director\web.config"
49 | Copy-Item -Path $webConfig -Destination ($LocalBackupFolder+"\"+$scriptTime.ToString("yyyy-MM-dd_HHmm_")+$server+"_web.config")
50 | $LogonAsp = "\\$server\c$\inetpub\wwwroot\director\LogOn.aspx"
51 | Copy-Item -Path $LogonAsp -Destination ($LocalBackupFolder+"\"+$scriptTime.ToString("yyyy-MM-dd_HHmm_")+$server+"_LogOn.aspx")
52 |
53 |
54 | #tracking file changes for web.config
55 | $FileChanges = $false
56 |
57 | $doc = (Get-Content $webConfig) -as [Xml]
58 | foreach ($key in $AppKeys.Keys) {
59 | $obj = $doc.configuration.appSettings.add | where {$_.Key -eq $key}
60 | if ($obj -eq $null -and $AppKeys[$key] -notlike "delete") {
61 | #Create a setting if it doesn't already exist (and is supposed to be there)
62 | $FileChanges = $true
63 | write-host "adding new config entry for $key"
64 | $newAppSetting = $doc.CreateElement("add")
65 | $doc.configuration.appSettings.AppendChild($newAppSetting) | Out-Null
66 | $newAppSetting.SetAttribute("key",$key);
67 | $newAppSetting.SetAttribute("value",$AppKeys[$key]);
68 | } elseif ($obj -ne $null -and $AppKeys[$key] -like "delete") {
69 | #Delete a setting if it's there and not supposed to be
70 | $FileChanges = $true
71 | write-host "removing config entry $key"
72 | $doc.configuration.appSettings.RemoveChild($obj) | Out-Null; # Remove the desired module when found
73 | } elseif ($obj -ne $null ) {
74 | #Update a setting if not already what it should be
75 | if($obj.value -notlike $AppKeys[$key]) {
76 | $FileChanges = $true
77 | write-host "Setting config entry for $key"
78 | $obj.value = $AppKeys[$key]
79 | }
80 | }
81 | }
82 | if ($FileChanges) {
83 | write-host "Saving web.config"
84 | $doc.Save($webConfig)
85 | }
86 |
87 |
88 | #tracking file changes for Logon.aspx
89 | $FileChanges = $false
90 |
91 | $text = gc $LogonAsp
92 | #Search the logon page for the entered domain name. If not found add a text field with the domain name
93 | if (($text | ? {$_ -imatch $DomainName} | measure).count -eq 0 -and $DomainName -ne "YOURDOMAIN") {
94 | $FileChanges = $true
95 | write-host "adding domain to logon page"
96 | $text = $text -replace 'ID="Domain"',('ID="Domain" text="'+$DomainName+'"')
97 | }
98 |
99 | #Search the logon page for the local machine name sting. If not found, add it in the footer.
100 | if (($text | ? {$_ -like "*String serverName = System.Environment.MachineName*"} | measure).count -eq 0) {
101 | $FileChanges = $true
102 | write-host "adding servername to logon page"
103 | $ReplaceWith = 'Citrix Systems, Inc.
104 | <% String serverName = System.Environment.MachineName; %>
105 |
<%=serverName %>
'
106 | $text = $text -ireplace "Citrix Systems, Inc.",$ReplaceWith
107 | }
108 | if ($FileChanges) {
109 | write-host "saving Logon.aspx"
110 | $text | Out-File $LogonAsp -Encoding utf8
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Director/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citrix/Powershell-Scripts/0757fc0960637a775c83e9588c02214c3c44a3fd/Director/README.md
--------------------------------------------------------------------------------
/PVS/LICENSE.MD:
--------------------------------------------------------------------------------
1 | Copyright 2017 Citrix Systems, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/PVS/README.MD:
--------------------------------------------------------------------------------
1 | # vDiskReplicator
2 |
3 | This script is intended to replicate vDisks/versions from a Store accessible by the "export" PVS server to other PVS servers in the same Farm/Site and another Farm/Store/Site.
4 |
5 | ## Getting Started
6 |
7 | All parameters ("export"/"import" servers, source/destination site, source/destination store, and disks to export) are either passed in or selected via the GUI.
8 |
9 | All read-only vDisks/versions from a user-defined Store on the export PVS server are replicated to all other servers in a user-defined Site in the same Farm.
10 |
11 | The same vDisks/versions are also replicated to a user-defined Store/Site (and all servers in that Site) in the same Farm as the import PVS server.
12 |
13 | Script includes basic error handling, but assumes import and export PVS servers are unique.
14 |
15 | Errors and information are written to the PowerShell host as well as the event log (Robocopy logs are also generated).
16 |
17 | ### Prerequisites
18 |
19 | Must first run on all PVS servers
20 |
21 | Must first run on the PVS server this script is run from
22 |
23 | Must first install PVS Console software on the PVS server this script is fun from
24 |
25 | Must use an account that is a local administrator on all PVS servers
26 |
27 | ## Built With
28 |
29 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
30 |
31 | ## NEW IN THIS VERSION (2.0):
32 |
33 | Bug Fixes -- fixed several bugs reported by the user community
34 |
35 | Intra-site Replication -- Running the script with the -INTRASITE switch (and required parameters -- see example below) will copy the .PVP, .VHD/X, and .AVHD/X files (including maintenance versions!) for each vDisk specified (or all vDisks on the server if the IS_vDisksToExport parameter is not used) from the IS_srcServer to all other PVS servers in the same site.
36 |
37 | ## EXAMPLES
38 |
39 | This script should be run (as administrator) on a PVS server after any vDisk or vDisk version is promoted to production.
40 |
41 | .\vDisk_Replicator_.ps1 -GUI
42 |
43 | .\vDisk_Replicator_.ps1 -srcServer -srcSite -srcStore -dstServer -dstSite -dstStore [-vDisksToExport ]
44 |
45 | .\vDisk_Replicator_.ps1 -INTRASITE -IS_srcServer [-IS_vDisksToExport ]
46 |
47 | ## Versioning & Authors
48 |
49 | VERSION
50 | 2.0
51 |
52 | DATE MODIFIED
53 | 6/19/2017
54 |
55 | AUTHOR
56 | Sam Breslow, Citrix Consulting
--------------------------------------------------------------------------------
/PVS/README.md:
--------------------------------------------------------------------------------
1 | # vDiskReplicator
2 |
3 | This script is intended to replicate vDisks/versions from a Store accessible by the "export" PVS server to other PVS servers in the same Farm/Site and another Farm/Store/Site.
4 |
5 | ## Getting Started
6 |
7 | All parameters ("export"/"import" servers, source/destination site, source/destination store, and disks to export) are either passed in or selected via the GUI.
8 |
9 | All read-only vDisks/versions from a user-defined Store on the export PVS server are replicated to all other servers in a user-defined Site in the same Farm.
10 |
11 | The same vDisks/versions are also replicated to a user-defined Store/Site (and all servers in that Site) in the same Farm as the import PVS server.
12 |
13 | Script includes basic error handling, but assumes import and export PVS servers are unique.
14 |
15 | Errors and information are written to the PowerShell host as well as the event log (Robocopy logs are also generated).
16 |
17 | ### Prerequisites
18 |
19 | Must first run on all PVS servers
20 |
21 | Must first run on the PVS server this script is run from
22 |
23 | Must first install PVS Console software on the PVS server this script is fun from
24 |
25 | Must use an account that is a local administrator on all PVS servers
26 |
27 | ## Built With
28 |
29 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
30 |
31 | ## NEW IN THIS VERSION (2.0):
32 |
33 | Bug Fixes -- fixed several bugs reported by the user community
34 |
35 | Intra-site Replication -- Running the script with the -INTRASITE switch (and required parameters -- see example below) will copy the .PVP, .VHD/X, and .AVHD/X files (including maintenance versions!) for each vDisk specified (or all vDisks on the server if the IS_vDisksToExport parameter is not used) from the IS_srcServer to all other PVS servers in the same site.
36 |
37 | ## EXAMPLES
38 |
39 | This script should be run (as administrator) on a PVS server after any vDisk or vDisk version is promoted to production.
40 |
41 | .\vDisk_Replicator_.ps1 -GUI
42 |
43 | .\vDisk_Replicator_.ps1 -srcServer -srcSite -srcStore -dstServer -dstSite -dstStore [-vDisksToExport ]
44 |
45 | .\vDisk_Replicator_.ps1 -INTRASITE -IS_srcServer [-IS_vDisksToExport ]
46 |
47 | ## Versioning & Authors
48 |
49 | VERSION
50 | 2.0
51 |
52 | DATE MODIFIED
53 | 6/19/2017
54 |
55 | AUTHOR
56 | Sam Breslow, Citrix Consulting
--------------------------------------------------------------------------------
/PVS/vDiskReplicator_v2.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | vDisk Replicator Script
4 |
5 | .DESCRIPTION
6 | This script is intended to replicate vDisks/versions from a Store accessible by the "export" PVS server to other PVS servers in the same Farm/Site and another Farm/Store/Site.
7 | All parameters ("export"/"import" servers, source/destination site, source/destination store, and disks to export) are either passed in or selected via the GUI.
8 | All read-only vDisks/versions from a user-defined Store on the export PVS server are replicated to all other servers in a user-defined Site in the same Farm.
9 | The same vDisks/versions are also replicated to a user-defined Store/Site (and all servers in that Site) in the same Farm as the import PVS server.
10 | Script includes basic error handling, but assumes import and export PVS servers are unique.
11 | Errors and information are written to the PowerShell host as well as the event log (Robocopy logs are also generated).
12 | Must first run on all PVS servers
13 | Must first run on the PVS server this script is run from
14 | Must first install PVS Console software on the PVS server this script is fun from
15 | Must use an account that is a local administrator on all PVS servers
16 |
17 | NEW in this version (2.0):
18 | Bug Fixes -- fixed several bugs reported by the user community
19 | Intra-site Replication -- Running the script with the -INTRASITE switch (and required parameters -- see example below) will copy the .PVP, .VHD/X, and .AVHD/X files
20 | (including maintenance versions!) for each vDisk specified (or all vDisks on the server if the IS_vDisksToExport parameter is not used) from the IS_srcServer to all other
21 | PVS servers in the same site.
22 |
23 | .EXAMPLE
24 | This script should be run (as administrator) on a PVS server after any vDisk or vDisk version is promoted to production.
25 | .\vDisk_Replicator_.ps1 -GUI
26 | .\vDisk_Replicator_.ps1 -srcServer -srcSite -srcStore -dstServer -dstSite -dstStore [-vDisksToExport ]
27 | .\vDisk_Replicator_.ps1 -INTRASITE -IS_srcServer [-IS_vDisksToExport ]
28 |
29 | .VERSION
30 | 2.0
31 |
32 | .DATE MODIFIED
33 | 6/19/2017
34 |
35 | .AUTHOR
36 | Sam Breslow, Citrix Consulting
37 | #>
38 |
39 | # /***************************************************************************************
40 | # *
41 | # * This software / sample code is provided to you “AS IS” with no representations,
42 | # * warranties or conditions of any kind. You may use, modify and distribute it at
43 | # * your own risk. CITRIX DISCLAIMS ALL WARRANTIES WHATSOEVER, EXPRESS, IMPLIED,
44 | # * WRITTEN, ORAL OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF
45 | # * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NONINFRINGEMENT.
46 | # * Without limiting the generality of the foregoing, you acknowledge and agree that
47 | # * (a) the software / sample code may exhibit errors, design flaws or other problems,
48 | # * possibly resulting in loss of data or damage to property; (b) it may not be
49 | # * possible to make the software / sample code fully functional; and (c) Citrix may,
50 | # * without notice or liability to you, cease to make available the current version
51 | # * and/or any future versions of the software / sample code. In no event should the
52 | # * software / sample code be used to support of ultra-hazardous activities, including
53 | # * but not limited to life support or blasting activities. NEITHER CITRIX NOR ITS
54 | # * AFFILIATES OR AGENTS WILL BE LIABLE, UNDER BREACH OF CONTRACT OR ANY OTHER THEORY
55 | # * OF LIABILITY, FOR ANY DAMAGES WHATSOEVER ARISING FROM USE OF THE SOFTWARE / SAMPLE
56 | # * CODE, INCLUDING WITHOUT LIMITATION DIRECT, SPECIAL, INCIDENTAL, PUNITIVE,
57 | # * CONSEQUENTIAL OR OTHER DAMAGES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
58 | # * Although the copyright in the software / sample code belongs to Citrix, any
59 | # * distribution of the code should include only your own standard copyright attribution,
60 | # * and not that of Citrix. You agree to indemnify and defend Citrix against any and
61 | # * all claims arising from your use, modification or distribution of the code.
62 | # *
63 | # ***************************************************************************************/
64 |
65 |
66 | #Define Parameters
67 | Param(
68 | [Parameter(ParameterSetName="GUI",Mandatory=$true)][switch]$gui,
69 | [Parameter(ParameterSetName="INTRASITE",Mandatory=$true)][switch]$INTRASITE,
70 | [Parameter(ParameterSetName="INTRASITE",Mandatory=$true)][string]$IS_srcServer,
71 | [Parameter(ParameterSetName="INTRASITE",Mandatory=$false)][array]$IS_vDisksToExport,
72 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$srcServer,
73 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$srcSite,
74 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$srcStore,
75 | [Parameter(ParameterSetName="CLI",Mandatory=$false)][array]$vDisksToExport,
76 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$dstServer,
77 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$dstSite,
78 | [Parameter(ParameterSetName="CLI",Mandatory=$true)][string]$dstStore
79 | )
80 |
81 | #Check for Administrator rights
82 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] “Administrator”)){
83 | Write-Host “You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!” -BackgroundColor Black -ForegroundColor Red
84 | Exit
85 | }
86 |
87 | #Initialize Event Log source
88 | $replication = "vDisk Replicator Script"
89 | try{
90 | $check = ([System.Diagnostics.EventLog]::SourceExists($replication))
91 | if (!$check){
92 | New-EventLog -LogName Application -Source $replication
93 | }
94 | $msg = "vDiskReplicator Script Initialized."
95 | Write-EventLog -LogName Application -Source $replication -EventId 1 -EntryType Information -Message $msg -Category 0
96 | }
97 | catch{
98 | Write-Host $_.Exception.GetType().FullName -BackgroundColor Black -ForegroundColor Red
99 | }
100 |
101 | #General Trap for unhandled errors
102 | trap {
103 | Write-Host “GENERAL ERROR, SEE EVENT LOG” -BackgroundColor Black -ForegroundColor Red
104 | $msg = "GENERAL ERROR: "+$_.Exception
105 | Write-EventLog -LogName Application -Source $replication -EventId 0 -EntryType Error -Message $msg -Category 0
106 | }
107 |
108 | #Import PVS PowerShell Module
109 | try{
110 | Import-Module 'C:\Program Files\Citrix\Provisioning Services console\Citrix.PVS.SnapIn.dll' -ErrorAction Stop
111 | }
112 | catch {
113 | Write-Host "PVS PowerShell Snap-In Not Found" -BackgroundColor Black -ForegroundColor Red
114 | $msg = "PVS PowerShell Snap-In Not Found on $env:computername. Script terminated."
115 | Write-EventLog -LogName Application -Source $replication -EventId 2 -EntryType Error -Message $msg -Category 0
116 | exit
117 | }
118 |
119 | #Define Functions
120 | function Create-Form($size){
121 | $Form = New-Object System.Windows.Forms.Form
122 | $Form.Size = $size
123 | $Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Program Files\Citrix\Provisioning Services Console\Console.msc")
124 | $form.Icon = $icon
125 | $form.Text = "vDisk Replicator Setup"
126 | $form.FormBorderStyle = 'FixedDialog' #MZ - Don't use fixed dialog, especially if you expect long paths
127 | $Form.Add_Shown({$Form.Activate()})
128 | return $Form
129 | }
130 |
131 | function Validate-Servers($srcServer, $dstServer){
132 | $msg = ""
133 | $exportMsg = ""
134 | $importMsg = ""
135 | try{
136 | Set-PvsConnection -Server $srcServer -Port 54321 -ErrorAction Stop | Out-Null
137 | } catch {
138 | $exportMsg = "Invalid Export Server: $srcServer"
139 | }
140 | try{
141 | Set-PvsConnection -Server $dstServer -Port 54321 -ErrorAction Stop | Out-Null
142 | } catch {
143 | $importMsg = "Invalid Import Server: $dstServer"
144 | }
145 | if($exportMsg -like ""){
146 | $msg = $importMsg
147 | } else {
148 | $msg = "$exportMsg, $importMsg"
149 | }
150 | return $msg
151 | }
152 |
153 | function Validate-SitesAndStores($server, $site, $store){
154 | $msg = ""
155 | try{
156 | Set-PvsConnection -Server $server -Port 54321 -ErrorAction Stop | Out-Null
157 | } catch {
158 | $msg = "Invalid Server: $server."
159 | exit
160 | }
161 | $siteExists = $true
162 | try{
163 | Get-PvsSite -SiteName $site | Out-Null
164 | } catch {
165 | $siteExists = $false
166 | }
167 | $storeExists = $true
168 | try{
169 | Get-PvsStore -StoreName $store | Out-Null
170 | } catch{
171 | $storeExists = $false
172 | }
173 | if(!$siteExists){
174 | $msg += " Invalid SERVER -> SITE combination: $server -> $site."
175 | }
176 | if(!$storeExists){
177 | $msg += " Invalid SERVER -> STORE combination: $server -> $store."
178 | }
179 | return $msg
180 | }
181 |
182 | function Validate-ExportList($server, $list){
183 | $msg = ""
184 | try{
185 | Set-PvsConnection -Server $server -Port 54321 -ErrorAction Stop | Out-Null
186 | } catch {
187 | $msg = "Invalid Server: $server."
188 | exit
189 | }
190 | if($list.Length -ne 0){
191 | $invalidDisks = ""
192 | $realDisks = Get-PvsDiskLocator | select-object -expandProperty DiskLocatorName
193 | $comparison = Compare-Object -ReferenceObject $realDisks -DifferenceObject $list
194 | foreach($c in $comparison){
195 | if($c.SideIndicator -like "=>"){
196 | $invalidDisks += $c.InputObject
197 | }
198 | }
199 | if($invalidDisks.Length -ne 0){
200 | $invalidDisks = $invalidDisks.TrimEnd(", ")
201 | $msg += "The following vDisk names are invalid: $invalidDisks"
202 | if($invalidDisks.Length -eq $list.Length){
203 | $msg +="`nThe ExportList must contain at least one valid vDisk."
204 | exit
205 | }
206 | }
207 | } else {
208 | $msg = "The ExportList must contain at least one vDisk."
209 | }
210 | return $msg
211 | }
212 |
213 | function Start-Robocopy($server, $srcPath, $dstPath, $disk, $cred, $eventLog){
214 | $fqdn = $server.serverFQDN
215 | $msg = "Begining Robocopy procedure for $disk on $fqdn"
216 | Write-EventLog -LogName Application -Source $eventLog -EventId 9 -EntryType Information -Message $msg -Category 0
217 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
218 | $d = $args[2]
219 | robocopy $args[0] $args[1] /np /xo /J "$d*.vhd" /MT 8 "/log:C:\vhdRepLog_$d" /v /b /R:5 /W:5
220 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.VHD replication"
221 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
222 | $d = $args[2]
223 | robocopy $args[0] $args[1] /np /xo /J "$d*.vhdx" /MT 8 "/log:C:\vhdxRepLog_$d" /v /b /R:5 /W:5
224 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.VHDX replication"
225 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
226 | $d = $args[2]
227 | robocopy $args[0] $args[1] /np /xo /J "$d*.avhd" /MT 8 "/log:C:\avhdRepLog_$d" /v /b /R:5 /W:5
228 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.AVHD replication"
229 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
230 | $d = $args[2]
231 | robocopy $args[0] $args[1] /np /xo /J "$d*.avhdx" /MT 8 "/log:C:\avhdxRepLog_$d" /v /b /R:5 /W:5
232 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.AVHDx replication"
233 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
234 | $d = $args[2]
235 | robocopy $args[0] $args[1] /np /xo /J "$d*.pvp" /MT 8 "/log:C:\pvpRepLog_$d" /v /R:5 /W:5
236 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.PVP replication"
237 | if(!$server.sameFarm){
238 | Invoke-Command -ComputerName $fqdn -ScriptBlock {
239 | $d = $args[2]
240 | robocopy $args[0] $args[1] /np /xo /J "$d*.xml" /MT 8 "/log:C:\xmlRepLog_$d" /v /b /R:5 /W:5
241 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.XML replication"
242 | }
243 | invoke-command -ComputerName $fqdn -scriptblock{
244 | $processes = Get-WmiObject -Class win32_process -Filter "name='robocopy.exe'"
245 | foreach($p in $processes){
246 | $p.setPriority(128)
247 | }
248 | } -Authentication Credssp -Credential $cred -AsJob -JobName "RoboCopy Process Priority"
249 | }
250 |
251 | function Start-ISRobocopy($server, $srcPath, $dstPath, $disk, $cred, $eventLog){
252 | $msg = "Begining Robocopy procedure for $disk on $server"
253 | Write-EventLog -LogName Application -Source $eventLog -EventId 9 -EntryType Information -Message $msg -Category 0
254 | Invoke-Command -ComputerName $server -ScriptBlock {
255 | $d = $args[2]
256 | robocopy $args[0] $args[1] /np /xo /J "$d*.vhd" /MT 8 "/log:C:\vhdRepLog_$d" /v /b /R:5 /W:5
257 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.VHD replication"
258 | Invoke-Command -ComputerName $server -ScriptBlock {
259 | $d = $args[2]
260 | robocopy $args[0] $args[1] /np /xo /J "$d*.vhdx" /MT 8 "/log:C:\vhdxRepLog_$d" /v /b /R:5 /W:5
261 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.VHDX replication"
262 | Invoke-Command -ComputerName $server -ScriptBlock {
263 | $d = $args[2]
264 | robocopy $args[0] $args[1] /np /xo /J "$d*.avhd" /MT 8 "/log:C:\avhdRepLog_$d" /v /b /R:5 /W:5
265 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.AVHD replication"
266 | Invoke-Command -ComputerName $server -ScriptBlock {
267 | $d = $args[2]
268 | robocopy $args[0] $args[1] /np /xo /J "$d*.avhdx" /MT 8 "/log:C:\avhdxRepLog_$d" /v /b /R:5 /W:5
269 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.AVHDx replication"
270 | Invoke-Command -ComputerName $server -ScriptBlock {
271 | $d = $args[2]
272 | robocopy $args[0] $args[1] /np /xo /J "$d*.pvp" /MT 8 "/log:C:\pvpRepLog_$d" /v /R:5 /W:5
273 | } -ArgumentList $srcPath, $dstPath, $disk -Credential $cred -Authentication Credssp -AsJob -JobName "$disk.PVP replication"
274 | invoke-command -ComputerName $server -scriptblock{
275 | $processes = Get-WmiObject -Class win32_process -Filter "name='robocopy.exe'"
276 | foreach($p in $processes){
277 | $p.setPriority(128)
278 | }
279 | } -Authentication Credssp -Credential $cred -AsJob -JobName "RoboCopy Process Priority"
280 | }
281 |
282 | function Start-Replication($cred, $srcServer, $srcSite, $srcStore, $vDisksToExport, $dstServer, $dstSite, $dstStore, $eventLog){
283 | #Begin Export Prerequisites
284 | $destinationServers = @()
285 | Set-PvsConnection -Server $srcServer -Port 54321 | Out-Null
286 | foreach($s in (Get-PvsServer -SiteName $srcSite | Select-Object -ExpandProperty ServerFQDN)){
287 | if($s -notlike $srcServer){
288 | $serverObj = New-Object -TypeName PSObject -Property (@{'ServerFQDN'=$s;'sameFarm'=$true})
289 | $destinationServers += $serverObj
290 | }
291 | }
292 | foreach($d in $vDisksToExport){
293 | try{
294 | $disk = Get-PvsDiskLocator -DiskLocatorName $d -StoreName $srcStore -SiteName $srcSite
295 | Export-PvsDisk -DiskLocatorId $disk.DiskLocatorId
296 | Write-Host "Exporting vDisk"$disk.Name
297 | $msg = "Exporting vDisk: "+$disk.Name
298 | Write-EventLog -LogName Application -Source $eventLog -EventId 7 -EntryType Information -Message $msg -Category 0
299 | } catch {
300 | Write-Host "Could not export"$_.Exception -BackgroundColor Black -ForegroundColor Red
301 | $msg = "Could not export "+$_.Exception
302 | Write-EventLog -LogName Application -Source $eventLog -EventId 8 -EntryType Error -Message $msg -Category 0
303 | $vDisksToExport = $vDisksToExport -ne $disk.name
304 | }
305 | }
306 | $srcStorePath = Get-PvsStore -StoreName $srcStore | Select-Object -ExpandProperty Path
307 | Set-PvsConnection -Server $dstServer -Port 54321 | Out-Null
308 | foreach($s in (Get-PvsServer -SiteName $dstSite | Select-Object -ExpandProperty ServerFQDN)){
309 | $serverObj = New-Object -TypeName PSObject -Property (@{'ServerFQDN'=$s;'sameFarm'=$false})
310 | $destinationServers += $serverObj
311 | }
312 | $dstStorePath = Get-PvsStore -StoreName $dstStore | Select-Object -ExpandProperty Path
313 | #Begin Robocopy
314 | $srcStorePath = $srcStorePath -replace ':','$'
315 | $srcPath = "\\$srcServer\$srcStorePath"
316 | $dstStorePath = $dstStorePath -replace ':','$'
317 | foreach($ds in $destinationServers){
318 | $dsFQDN = $ds.ServerFQDN
319 | $dstPath = "\\$dsFQDN\$dstStorePath"
320 | foreach($vdisk in $vDisksToExport){
321 | Write-host "CALLING ROBOCOPY"
322 | if($ds.sameFarm){
323 | Start-Robocopy -server $ds -srcPath $srcPath -dstPath "\\$dsFQDN\$srcStorePath" -disk $vdisk -cred $cred -eventLog $eventLog
324 | } else {
325 | Start-Robocopy -server $ds -srcPath $srcPath -dstPath $dstPath -disk $vdisk -cred $cred -eventLog $eventLog
326 | }
327 | }
328 | }
329 | #Wait for all Robocopy processes to complete before attempting to import/add vDisks/versions
330 | Wait-Job -State Running
331 | #Begin Import
332 | #Gather Destination Farm vDisks/versions to determine if exported vDisks/versions need to be imported or added
333 | $dstDisks = Get-PvsDiskLocator -SiteName $dstSite
334 | foreach($vdisk in $vDisksToExport){
335 | foreach($d in $dstDisks){
336 | if($vdisk -like $d.Name){
337 | try{
338 | Add-PvsDiskVersion -StoreName $dstStore -SiteName $dstSite -Name $vdisk
339 | } catch {
340 | Write-host "Could not import vDisk versions for"$_.Exception -BackgroundColor Black -ForegroundColor Red
341 | $msg = "Could not import vDisk versions for "+$_.Exception
342 | Write-EventLog -LogName Application -Source $eventLog -EventId 10 -EntryType Error -Message $msg -Category 0
343 | }
344 | break;
345 | }
346 | }
347 | if(Test-Path "\\$dstserver\$dststorepath\$vdisk*.vhdx"){
348 | Write-Host "VHDX IMPORT"
349 | try{
350 | Import-PvsDisk -StoreName $dstStore -SiteName $dstSite -Name $vdisk -VHDX
351 | } catch {
352 | Write-host "Could not import vDisk for"$_.Exception -BackgroundColor Black -ForegroundColor Red
353 | $msg = "Could not import vDisk for "+$_.Exception
354 | Write-EventLog -LogName Application -Source $eventLog -EventId 11 -EntryType Error -Message $msg -Category 0
355 | }
356 | } else {
357 | Write-Host "VHD IMPORT"
358 | try{
359 | Import-PvsDisk -StoreName $dstStore -SiteName $dstSite -Name $vdisk
360 | } catch {
361 | Write-host "Could not import vDisk for"$_.Exception -BackgroundColor Black -ForegroundColor Red
362 | $msg = "Could not import vDisk for "+$_.Exception
363 | Write-EventLog -LogName Application -Source $eventLog -EventId 11 -EntryType Error -Message $msg -Category 0
364 | }
365 | }
366 | }
367 | }
368 |
369 | function out-cli($srcServer, $srcSite, $srcStore, $vDisksToExport, $dstServer, $dstSite, $dstStore){
370 | $outpath = pwd
371 | $outpath = $outpath.Path
372 | $line = '$srcServer = '+"'$srcServer'"
373 | $line > "$outpath\vDiskReplicatorCLI.ps1"
374 | $line = '$srcSite = '+"'$srcSite'"
375 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
376 | $line = '$srcStore = '+"'$srcStore'"
377 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
378 |
379 | $line = '$dstServer = '+"'$dstServer'"
380 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
381 | $line = '$dstSite = '+"'$dstSite'"
382 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
383 | $line = '$dstStore = '+"'$dstStore'"
384 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
385 |
386 | $line = '$vDisksToExport = @()'
387 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
388 | foreach($disk in $vDisksToExport){
389 | $line = '$vDisksToExport += '+"'$disk'"
390 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
391 | }
392 |
393 | $line = '.\vDiskReplicator.ps1 -srcServer $srcServer -srcSite $srcSite -srcStore $srcStore -dstServer $dstServer -dstSite $dstSite -dstStore $dstStore -vDisksToExport $vDisksToExport'
394 | $line >> "$outpath\vDiskReplicatorCLI.ps1"
395 | }
396 |
397 | ### START SCRIPT
398 |
399 | if($INTRASITE){
400 | $msg = Validate-Servers -srcServer $IS_srcServer -dstServer $IS_srcServer
401 | if($msg -notlike ""){
402 | Write-Host $msg -BackgroundColor Black -ForegroundColor Red
403 | $msg += ". Script Terminated."
404 | Write-EventLog -LogName Application -Source $replication -EventId 3 -EntryType Error -Message $msg -Category 0
405 | exit
406 | }
407 | Set-PvsConnection -Server $IS_srcServer -Port 54321 -ErrorAction Stop | Out-Null
408 | $IS_srvObj = Get-PvsServer -ServerName $IS_srcServer.Split('.')[0]
409 | $IS_site = Get-PvsSite -SiteName $IS_srvObj.SiteName
410 | $IS_storeID = (Get-PvsServerStore -ServerName $IS_srcServer.Split('.')[0]).StoreID.ToString()
411 | $IS_storePath = (Get-PvsStore -StoreId $IS_storeID).Path
412 | $IS_dstServers = @()
413 | foreach($s in (Get-PvsServer -SiteName $IS_site.SiteName | Select-Object -ExpandProperty ServerFQDN)){
414 | if($s -notlike $IS_srcServer){
415 | $IS_dstServers += $s
416 | }
417 | }
418 | ##CHECK VDISKS
419 | if($vDisksToExport.Length -ne 0){
420 | $exportMsg = Validate-ExportList -server $srcServer -list $vDisksToExport
421 | if($exportMsg -notlike ""){
422 | Write-Host $exportMSG -BackgroundColor Black -ForegroundColor Red
423 | $exportMsg += ". Script Terminated."
424 | Write-EventLog -LogName Application -Source $replication -EventId 5 -EntryType Error -Message $exportMsg -Category 0
425 | exit
426 | }
427 | }
428 | else{
429 | $exportMsg = "No list of vDisks to export found. Exporting ALL vDisks found on $IS_srcServer."
430 | Write-EventLog -LogName Application -Source $replication -EventId 6 -EntryType Warning -Message $exportMsg -Category 0
431 | $IS_vDisksToExport = Get-PvsDiskLocator -StoreID $IS_StoreID -SiteName $IS_Site.SiteName | Select-Object -ExpandProperty DiskLocatorName
432 | }
433 | $IS_StorePath = $IS_StorePath -replace ':','$'
434 | $IS_srcPath = "\\$IS_srcServer\$IS_StorePath"
435 | $cred = Get-Credential
436 | foreach($s in $IS_dstServers){
437 | $IS_dstPath = "\\$s\$IS_StorePath"
438 | foreach($vdisk in $IS_vDisksToExport){
439 | Start-ISRobocopy -server $s -srcPath $IS_srcPath -dstPath $IS_dstPath -disk $vdisk -cred $cred -eventLog $replication
440 | }
441 | }
442 | Wait-Job -State Running
443 | Write-Host DONE
444 | }elseif($gui){
445 | #Load GUI modules
446 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
447 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
448 | $Form = Create-Form(New-Object System.Drawing.Size(500,220))
449 | $Form.Text += " (1 of 3)"
450 | $srcFarmLabel = New-Object System.Windows.Forms.Label
451 | $srcFarmLabel.Text = "Enter Export PVS Server FQDN:"
452 | $srcFarmLabel.AutoSize = $true
453 | $srcFarmLabel.Location = New-Object System.Drawing.Size(0,15)
454 | $form.Controls.Add($srcFarmLabel)
455 | $srcFarmTextBox = New-Object System.Windows.Forms.textBox
456 | $srcFarmTextBox.Location = New-Object System.Drawing.Size(5,45)
457 | $srcFarmTextBox.Size = New-Object System.Drawing.Size(300,20)
458 | $srcFarmTextBox.Text = ""
459 | $Form.Controls.Add($srcFarmTextBox)
460 |
461 | $dstFarmLabel = New-Object System.Windows.Forms.Label
462 | $dstFarmLabel.Text = "Enter Import PVS Server FQDN:"
463 | $dstFarmLabel.AutoSize = $true
464 | $dstFarmLabel.Location = New-Object System.Drawing.Size(0,75)
465 | $form.Controls.Add($dstFarmLabel)
466 | $dstFarmTextBox = New-Object System.Windows.Forms.textBox
467 | $dstFarmTextBox.Location = New-Object System.Drawing.Size(5,105)
468 | $dstFarmTextBox.Size = New-Object System.Drawing.Size(300,20)
469 | $dstFarmTextBox.Text = ""
470 | $Form.Controls.Add($dstFarmTextBox)
471 |
472 | $outputBox = New-Object System.Windows.Forms.Label
473 | $outputBox.Location = New-Object System.Drawing.Size(5,150)
474 | $outputBox.Size = New-Object System.Drawing.Size(460,20)
475 | $Form.Controls.Add($outputBox)
476 |
477 | $Button = New-Object System.Windows.Forms.Button
478 | $Button.Location = New-Object System.Drawing.Size(350,30)
479 | $Button.Size = New-Object System.Drawing.Size(110,80)
480 | $Button.Text = "Next"
481 | $Button.Add_Click({
482 | $srcServer = $srcFarmTextBox.Text
483 | $dstServer = $dstFarmTextBox.Text
484 | $msg = Validate-Servers -srcServer $srcServer -dstServer $dstServer
485 | #$msg = "" #REMOVE THIS -- FOR TESTING ONLY
486 | if($msg -like ""){
487 | $Form.Close() | Out-Null
488 | $Form = Create-Form(New-Object System.Drawing.Size(500,220))
489 | $Form.Text += " (2 of 3)"
490 | #Create Source Store Drop Down Box
491 | $srcStoreLabel = New-Object System.Windows.Forms.Label
492 | $srcStoreLabel.Text = "Select Source Store:"
493 | $srcStoreLabel.AutoSize = $true
494 | $srcStoreLabel.Location = New-Object System.Drawing.Size(0,15)
495 | $form.Controls.Add($srcStoreLabel)
496 | $srcStoreDropDownBox = New-Object System.Windows.Forms.ComboBox
497 | $srcStoreDropDownBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
498 | $srcStoreDropDownBox.Location = New-Object System.Drawing.Size(130,10)
499 | $srcStoreDropDownBox.Size = New-Object System.Drawing.Size(200,20)
500 | $srcStoreDropDownBox.DropDownHeight = 200
501 | $Form.Controls.Add($srcStoreDropDownBox)
502 | Set-PvsConnection -Server $srcServer -Port 54321
503 | $srcStores = Get-PvsStore
504 | foreach ($s in $srcStores){
505 | $srcStoreDropDownBox.Items.Add($s.StoreName) | Out-Null
506 | }
507 | #Create Source Site Drop Down Box
508 | $srcSiteLabel = New-Object System.Windows.Forms.Label
509 | $srcSiteLabel.Text = "Select Source Site:"
510 | $srcSiteLabel.AutoSize = $true
511 | $srcSiteLabel.Location = New-Object System.Drawing.Size(0,45)
512 | $form.Controls.Add($srcSiteLabel)
513 | $srcSiteDropDownBox = New-Object System.Windows.Forms.ComboBox
514 | $srcSiteDropDownBox.Location = New-Object System.Drawing.Size(130,40)
515 | $srcSiteDropDownBox.Size = New-Object System.Drawing.Size(200,20)
516 | $srcSiteDropDownBox.DropDownHeight = 200
517 | $Form.Controls.Add($srcSiteDropDownBox)
518 | $srcSites = Get-PvsSite
519 | foreach ($s in $srcSites){
520 | $srcSiteDropDownBox.Items.Add($s.SiteName) | Out-Null
521 | }
522 | #Create Destination Store Drop Down Box
523 | $dstStoreLabel = New-Object System.Windows.Forms.Label
524 | $dstStoreLabel.Text = "Select Destination Store:"
525 | $dstStoreLabel.AutoSize = $true
526 | $dstStoreLabel.Location = New-Object System.Drawing.Size(0,75)
527 | $form.Controls.Add($dstStoreLabel)
528 | $dstStoreDropDownBox = New-Object System.Windows.Forms.ComboBox
529 | $dstStoreDropDownBox.Location = New-Object System.Drawing.Size(130,70)
530 | $dstStoreDropDownBox.Size = New-Object System.Drawing.Size(200,20)
531 | $dstStoreDropDownBox.DropDownHeight = 200
532 | $Form.Controls.Add($dstStoreDropDownBox)
533 | Set-PvsConnection -Server $dstServer -Port 54321
534 | $dstStores = Get-PvsStore
535 | foreach ($d in $dstStores){
536 | $dstStoreDropDownBox.Items.Add($d.StoreName) | Out-Null
537 | }
538 | #Create Destination Site Drop Down Box
539 | $dstSiteLabel = New-Object System.Windows.Forms.Label
540 | $dstSiteLabel.Text = "Select Destination Site:"
541 | $dstSiteLabel.AutoSize = $true
542 | $dstSiteLabel.Location = New-Object System.Drawing.Size(0,105)
543 | $form.Controls.Add($dstSiteLabel)
544 | $dstSiteDropDownBox = New-Object System.Windows.Forms.ComboBox
545 | $dstSiteDropDownBox.Location = New-Object System.Drawing.Size(130,100)
546 | $dstSiteDropDownBox.Size = New-Object System.Drawing.Size(200,20)
547 | $dstSiteDropDownBox.DropDownHeight = 200
548 | $Form.Controls.Add($dstSiteDropDownBox)
549 | $dstSites = Get-PvsSite
550 | foreach ($d in $dstSites){
551 | $dstSiteDropDownBox.Items.Add($d.SiteName) | Out-Null
552 | }
553 | #Add Output Box to Form
554 | $outputBox = New-Object System.Windows.Forms.Label
555 | $outputBox.Location = New-Object System.Drawing.Size(10,150)
556 | $outputBox.Size = New-Object System.Drawing.Size(460,20)
557 | $Form.Controls.Add($outputBox)
558 | #Add "Select" Button to Form
559 | $Button = New-Object System.Windows.Forms.Button
560 | $Button.Location = New-Object System.Drawing.Size(350,30)
561 | $Button.Size = New-Object System.Drawing.Size(110,80)
562 | $Button.Text = "Select"
563 | $Button.Add_Click({
564 | $srcStore = $srcStoreDropDownBox.SelectedItem
565 | $srcSite = $srcSiteDropDownBox.SelectedItem
566 | $dstStore = $dstStoreDropDownBox.SelectedItem
567 | $dstSite = $dstSiteDropDownBox.SelectedItem
568 | if(($srcStore -ne $null) -and ($srcSite -ne $null) -and ($dstStore -ne $null) -and ($dstSite -ne $null)){
569 | $Form.Close() | Out-Null
570 | $Form = Create-Form(New-Object System.Drawing.Size(500,220))
571 | $Form.Text += " (3 of 3)"
572 | $cbLabel = New-Object System.Windows.Forms.Label
573 | $cbLabel.Text = "Select vDisks to export:"
574 | $cbLabel.AutoSize = $true
575 | $cbLabel.Location = New-Object System.Drawing.Size(10,10)
576 | $form.Controls.Add($cbLabel)
577 |
578 | Set-PvsConnection -Server $srcServer -Port 54321
579 | $disks = Get-PvsDiskLocator -StoreName $srcStore -SiteName $srcSite
580 | $checkBoxes = @()
581 | $counter = 0
582 | foreach($d in $disks){
583 | $cb = new-object System.Windows.Forms.checkbox
584 | $cb.Size = new-object System.Drawing.Size(250,50)
585 | $y = 30+(35*$counter)
586 | $cb.Location = new-object System.Drawing.Size(10,$y)
587 | $cb.Text = $d.disklocatorname
588 | $cb.Checked = $true
589 | $checkBoxes += $cb
590 | $Form.Controls.Add($cb)
591 | $counter += 1
592 | }
593 | $y+=110+70
594 | $Form.Size = New-Object System.Drawing.Size(500,$y)
595 | $newY = $y
596 |
597 | #Add Output Box to Form
598 | $y-=60
599 | $cbOutputBox = New-Object System.Windows.Forms.Label
600 | $cbOutputBox.Location = New-Object System.Drawing.Size(10,$y)
601 | $cbOutputBox.Size = New-Object System.Drawing.Size(460,20)
602 | $Form.Controls.Add($cbOutputBox)
603 |
604 | $y-=50
605 | $cbButton = New-Object System.Windows.Forms.Button
606 | $cbButton.Location = New-Object System.Drawing.Size(350,30)
607 | $cbButton.Size = New-Object System.Drawing.Size(110,$y)
608 | $cbButton.Text = "Start"
609 | $cbButton.Add_Click({
610 | $vDisksToExport = @()
611 | foreach($i in $checkBoxes){
612 | if($i.Checked){
613 | $vDisksToExport += $i.Text
614 | }
615 | }
616 | if($vDisksToExport.length -eq 0){
617 | $cbOutputBox.Text = "Select at least one vDisk"
618 | } else{
619 | $cred = Get-Credential DOMAIN\USER
620 | START-REPLICATION -cred $cred -srcServer $srcServer -srcSite $srcSite -srcStore $srcStore -vDisksToExport $vDisksToExport -dstServer $dstServer -dstSite $dstSite -dstStore $dstStore -eventLog $replication
621 | $Form.close() | Out-Null
622 | $finalmsg = "vDisk Replication Script Complete."
623 | Write-EventLog -LogName Application -Source $replication -EventId 11 -EntryType Information -Message $finalmsg -Category 0
624 | }
625 | })
626 |
627 | $y=$cbOutputBox.Location.Y+$cboutputbox.Size.Height+20
628 | $txtButton = New-Object System.Windows.Forms.Button
629 | $txtButton.Location = New-Object System.Drawing.Size(350,$y)
630 | $txtButton.Size = New-Object System.Drawing.Size(110,30)
631 | $txtbutton.Text = "Output CLI"
632 | $txtButton.Add_click({
633 | $vDisksToExport = @()
634 | foreach($i in $checkBoxes){
635 | if($i.Checked){
636 | $vDisksToExport += $i.Text
637 | }
638 | }
639 | if($vDisksToExport.length -eq 0){
640 | $cbOutputBox.Text = "Select at least one vDisk"
641 | } else{
642 | out-cli -srcServer $srcServer -srcSite $srcSite -srcStore $srcStore -vDisksToExport $vDisksToExport -dstServer $dstServer -dstSite $dstSite -dstStore $dstStore
643 | }
644 | })
645 | $newy+=50
646 | $Form.Size = New-Object System.Drawing.Size(500,$newy)
647 |
648 | $form.Controls.Add($cbButton)
649 | $form.Controls.Add($txtButton)
650 | $form.showDialog()
651 | } else {
652 | $outputBox.Text="Select a Valid Option"
653 | }
654 | })
655 | $Form.Controls.Add($Button)
656 | $Form.showDialog()
657 | } else{
658 | $outputBox.Text = $msg
659 | }
660 | })
661 | $Form.Controls.Add($Button)
662 | $Form.showDialog()
663 | }
664 | else {
665 | $msg = Validate-Servers -srcServer $srcServer -dstServer $dstServer
666 | if($msg -notlike ""){
667 | Write-Host $msg -BackgroundColor Black -ForegroundColor Red
668 | $msg += ". Script Terminated."
669 | Write-EventLog -LogName Application -Source $replication -EventId 3 -EntryType Error -Message $msg -Category 0
670 | exit
671 | }
672 | $srcMsg = Validate-SitesAndStores -server $srcServer -site $srcSite -store $srcStore
673 | $dstMsg = Validate-SitesAndStores -server $dstServer -site $dstSite -store $dstStore
674 | if($srcMsg -notlike ""){
675 | Write-Host $srcMsg -BackgroundColor Black -ForegroundColor Red
676 | $srcMsg += " Script Terminated."
677 | Write-EventLog -LogName Application -Source $replication -EventId 4 -EntryType Error -Message $srcmsg -Category 0
678 | exit
679 | }
680 | if($dstMsg -notlike ""){
681 | Write-Host $dstMsg -BackgroundColor Black -ForegroundColor Red
682 | $dstMsg += " Script Terminated."
683 | Write-EventLog -LogName Application -Source $replication -EventId 4 -EntryType Error -Message $dstmsg -Category 0
684 | exit
685 | }
686 | if(($srcMsg -like "") -and ($dstMsg -like "")){
687 | if($vDisksToExport.Length -ne 0){
688 | $exportMsg = Validate-ExportList -server $srcServer -list $vDisksToExport
689 | if($exportMsg -notlike ""){
690 | Write-Host $exportMSG -BackgroundColor Black -ForegroundColor Red
691 | $exportMsg += ". Script Terminated."
692 | Write-EventLog -LogName Application -Source $replication -EventId 5 -EntryType Error -Message $exportMsg -Category 0
693 | exit
694 | }
695 | } else {
696 | Set-PvsConnection -Server $srcServer -Port 54321 | Out-Null
697 | $exportMsg = "No list of vDisks to export found. Exporting ALL vDisks in the $srcStore vDisk store on $srcServer."
698 | Write-EventLog -LogName Application -Source $replication -EventId 6 -EntryType Warning -Message $exportMsg -Category 0
699 | $vDisksToExport = Get-PvsDiskLocator -StoreName $srcStore -SiteName $srcSite | Select-Object -ExpandProperty DiskLocatorName
700 | }
701 | $cred = Get-Credential $env:userDomain\$env:userName
702 | START-REPLICATION -cred $cred -srcServer $srcServer -srcSite $srcSite -srcStore $srcStore -vDisksToExport $vDisksToExport -dstServer $dstServer -dstSite $dstSite -dstStore $dstStore -eventLog $replication
703 | $finalmsg = "vDisk Replication Script Complete."
704 | Write-EventLog -LogName Application -Source $replication -EventId 11 -EntryType Information -Message $finalmsg -Category 0
705 | } else {
706 | #SHOULDN"T EVER BE HERE
707 | Throw "UNEXPECTED ERROR"
708 | }
709 | }
710 |
711 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Powershell-Scripts
2 |
3 | This repository hosts powershell scripts that target a variety of Citrix products. The script provided here are as is and not officially supported by Citrix Support. Please refer to each script folder for specifics on license and supportability.
4 |
5 | ## Getting Started
6 |
7 | The easiest way to get started using these scripts is to clone the repository to your local machine.
8 |
9 | ***As with all scripts you download, review the script you are interested in prior to running it so you understand the impact to your environment.***
10 |
11 | ```
12 | git clone https://github.com/citrix/Powershell-Scripts.git
13 | ```
14 |
15 | ### Prerequisites
16 |
17 | The base prerequisite for these script is [Microsoft Powershell](https://msdn.microsoft.com/powershell), but please see each individual script folder for specific script prerequisites (product, API access, tools, etc)
18 |
19 | ## Built With
20 |
21 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
22 |
23 | ## Contributing
24 |
25 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, contributing and the process for submitting pull requests.
26 |
27 | ## Versioning
28 |
29 | Scripts in the repository should use [SemVer](http://semver.org/) for versioning.
30 |
31 | ## Authors
32 |
33 | See the individual script folders for the specific script contributors/authors
34 |
35 | ## License
36 |
37 | Each script and folder has its specific license, look for a license.md. Please refer to those for license and attributions
38 |
39 |
--------------------------------------------------------------------------------
/Storefront/Create-Storefront3-Store.ps1:
--------------------------------------------------------------------------------
1 | # /*************************************************************************
2 | # *
3 | # * THIS SAMPLE CODE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
4 | # * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5 | # * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
6 | # *
7 | # *************************************************************************/
8 |
9 | $SiteID = 1
10 | $AuthenticationVirtualPath = "/Citrix/Authentication"
11 | $authSummary = Get-DSAuthenticationServiceSummary -SiteID $SiteID -VirtualPath $AuthenticationVirtualPath
12 | Install-DSStoreServiceAndConfigure -siteId $SiteId `
13 | -friendlyName $StoreFriendlyName `
14 | -virtualPath $StoreVirtualPath `
15 | -authSummary $authSummary `
16 | -farmName $FarmName `
17 | -servicePort $Port `
18 | -transportType $TransportType `
19 | -servers $FarmServers `
20 | -farmType $FarmType `
21 | -loadBalance $LoadBalance
22 |
--------------------------------------------------------------------------------
/Storefront/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/citrix/Powershell-Scripts/0757fc0960637a775c83e9588c02214c3c44a3fd/Storefront/README.md
--------------------------------------------------------------------------------
/XAXD/AutoscaleMcs/Invoke-AutoscaleMachineCreation.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Monitors current machine usage and creates or deletes machines based on
4 | administrator configured values.
5 |
6 | .DESCRIPTION
7 | This script is meant to monitor load accross a Citrix Delivery Group and
8 | create\delete machines when crossing the configured watermarks.
9 |
10 | It runs as a per-run driven state machine, which means it will only process
11 | one state per run of the script. This means the script should be run at
12 | regular intervals in order to monitor, create, and delete machines as needed.
13 |
14 | The first run of the script must contain ALL parameters except the script tag
15 | (if the default is desired). On subsequent runs, only the DeliveryGroupName is
16 | required, and optionally watermarks can be updated. Both watermarks must be
17 | updated at once.
18 |
19 | There are some prerequisites in order to run the script:
20 | 1. There must be a delivery group created to be monitored
21 | 2. There must be a machine catalog that is of type MCS and has an associated Provisioning Scheme (image)
22 | 3. The Provisioning Scheme must have an associated IdentityPool
23 | 4. Writing to Event Logs requires an event log source to be created before passing
24 | it in to this script. See the New-EventLog cmdlet.
25 | 5. In order to handle remote authentication, you must set up an API Key and set the
26 | credentials (using the same User Account under which the script will run).
27 | The required command looks like this:
28 | 'Set-XDCredentials -APIKey -CustomerId -SecretKey '
29 | where you will pass the to this script under the XdProfileName parameter.
30 | (see Set-XDCredentials)
31 |
32 | If setting this up with Task Scheduler, make sure to enable the setting that
33 | does not start a new instance if this one is already running.
34 |
35 | .PARAMETER DeliveryGroupName
36 | Semi-colon-separated list of the Delivery Group Name(s) to monitor and take actions on.
37 | When performining initialization or updating (e.g. passing in Watermarks, MaximumCreatedMachines, etc.)
38 | only one Delivery Group at a time is supported. The exception is 'EventLogSource' in which
39 | case it will be set for all the passed in Delivery Groups.
40 | .PARAMETER XdProfileName
41 | Name of the profile to use for remote authentication with Citrix Servers
42 | .PARAMETER LowWatermark
43 | Load percentage at which previously script-created machines in the given
44 | delivery group will be deleted
45 | .PARAMETER HighWatermark
46 | Load percentage at which new machines will be created and deployed
47 | .PARAMETER MachineCatalogName
48 | Name of the machine catalog where machines will be created
49 | .PARAMETER ScriptTag
50 | Tag that will be used to track the created machines
51 | .PARAMETER EventLogSource
52 | Name of the Event Log Source to write to. It is important to pass a meaningful
53 | name here in order to find the logs.
54 | .PARAMETER MaximumCreatedMachines
55 | The maximum amount of machines that will be created in the specified delivery group
56 |
57 | .NOTES
58 | Version : 1.0.1
59 | Author : Citrix Systems, Inc.
60 | Creation Date: 07 November 2019
61 | Log : Added MaximumCreatedMachines parameter
62 |
63 | .EXAMPLE
64 | Invoke-AutoscaleMachineCreation -DeliveryGroupName DevTest -XdProfileName TestProfile
65 | #>
66 |
67 | # ============================================================================
68 | # Copyright Citrix Systems, Inc. All rights reserved.
69 | # ============================================================================
70 |
71 | param(
72 | [Parameter(Mandatory=$true)]
73 | [String]$DeliveryGroupName,
74 | [Parameter(Mandatory=$true)]
75 | [String]$XdProfileName,
76 | [Parameter(Mandatory=$false)]
77 | [Int]$LowWatermark = -1,
78 | [Parameter(Mandatory=$false)]
79 | [Int]$HighWatermark = -1,
80 | [Parameter(Mandatory=$false)]
81 | [String]$MachineCatalogName,
82 | [Parameter(Mandatory=$false)]
83 | [String]$ScriptTag = 'AutoscaleScripted',
84 | [Parameter(Mandatory=$false)]
85 | [String]$EventLogSource = $null,
86 | [Parameter(Mandatory=$false)]
87 | [Int]$MaximumCreatedMachines = -1
88 | )
89 |
90 | # ============================================================================
91 | # State Machine
92 | # ============================================================================
93 |
94 | # Definitions ----------------------------------------------------------------
95 | Add-Type -TypeDefinition @"
96 | public enum AutoscaleScriptState
97 | {
98 | MonitorUsage,
99 | ProvisionMachines,
100 | MonitorProvision,
101 | AddMachines,
102 | RemoveMachines,
103 | MonitorDeleteMachines
104 | }
105 | "@
106 |
107 | # MetaData Names -------------------------------------------------------------
108 | $AutoscaleMetadataNames = @{
109 | 'State' = 'Citrix_AutoscaleScript_State';
110 | 'CleanExit' = 'Citrix_AutoscaleScript_CleanExit';
111 | 'Tag' = 'Citrix_AutoscaleScript_MachineTag';
112 | 'MachineCatalogName' = 'Citrix_AutoscaleScript_MachineCatalogName';
113 | 'IdentityPoolUid' = 'Citrix_AutoscaleScript_IdentityPoolUid';
114 | 'ProvSchemeUid' = 'Citrix_AutoscaleScript_ProvSchemeUid';
115 | 'ProvTask' = 'Citrix_AutoscaleScript_ProvTaskId';
116 | 'HighWatermark' = 'Citrix_AutoscaleScript_HighWatermark';
117 | 'LowWatermark' = 'Citrix_AutoscaleScript_LowWatermark';
118 | 'MaximumCreatedMachines' = 'Citrix_AutoscaleScript_MaximumCreatedMachines';
119 | 'CurrentLoad' = 'Citrix_AutoscaleScript_CurrentLoad';
120 | 'Actions' = 'Citrix_AutoscaleScript_ActionsTaken';
121 | 'EventLogSource' = 'Citrix_AutoscaleScript_EventLogSource';
122 | 'LastUpdateTime' = 'Citrix_AutoscaleScript_LastUpdateTime';
123 | }
124 |
125 | # States ---------------------------------------------------------------------
126 | function Watch-AutoscaleLoadBalance
127 | {
128 | param([object]$DeliveryGroup)
129 |
130 | if ($DeliveryGroup.TotalDesktops -le 0)
131 | {
132 | # No action to take in an empty delivery group
133 | return
134 | }
135 |
136 | $currentLoad = [math]::Round((Measure-DeliveryGroupLoad $DeliveryGroup), 2)
137 | $lowWm = [int]($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['LowWatermark']])
138 | $highWm = [int]($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['HighWatermark']])
139 |
140 | if ($currentLoad -gt $highWm)
141 | {
142 | $machines = @(Get-BrokerMachine -Tag ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['Tag']]) -DesktopGroupUid $DeliveryGroup.Uid -Property Uid)
143 | $maxCount = [int]($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['MaximumCreatedMachines']])
144 |
145 | if ($maxCount -gt 0 -and $machines.Count -ge $maxCount)
146 | {
147 | # We cannot create any more machines
148 | Write-Log "Cannot provision any more machines. Upper limit of [$maxCount] has been reached." -DgName $DeliveryGroup.Name
149 | return
150 | }
151 |
152 | Write-Log "Provisioning more machines. Current Usage [$currentLoad] >= High Watermark [$highWm]." -DgName $DeliveryGroup.Name
153 | $map = @{ `
154 | ($AutoscaleMetadataNames['CurrentLoad']) = [string]($currentLoad); `
155 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::ProvisionMachines); `
156 | }
157 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
158 | }
159 | elseif ($currentLoad -lt $lowWm)
160 | {
161 | $machines = @(Get-BrokerMachine -Tag ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['Tag']]) -DesktopGroupUid $DeliveryGroup.Uid -SessionCount 0 -Property Uid)
162 | if ($machines.Count -eq 0)
163 | {
164 | # There are no machines available to be removed
165 | return
166 | }
167 |
168 | Write-Log "Removing extraneous machines: Current Usage [$currentLoad] <= Low Watermark [$lowWm]." -DgName $DeliveryGroup.Name
169 | $map = @{ `
170 | ($AutoscaleMetadataNames['CurrentLoad']) = [string]($currentLoad); `
171 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::RemoveMachines); `
172 | }
173 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
174 | }
175 | }
176 |
177 | function Publish-Machines
178 | {
179 | param([object]$DeliveryGroup)
180 |
181 | $maxCount = [int]($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['MaximumCreatedMachines']])
182 | $machinesToCreate = (Measure-HighWatermarkDelta $DeliveryGroup)
183 |
184 | if ($maxCount -gt 0)
185 | {
186 | $machines = @(Get-BrokerMachine -Tag ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['Tag']]) -DesktopGroupUid $DeliveryGroup.Uid -Property Uid)
187 | $machinesToCreate = [math]::Min(($maxCount - $machines.Count), $machinesToCreate)
188 |
189 | if ($machinesToCreate -le 0)
190 | {
191 | Write-Log "Cannot provision any more machines, reached limit of [$maxCount]" -DgName $DeliveryGroup.Name
192 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
193 | return
194 | }
195 | }
196 |
197 | $adAccounts = New-AcctADAccount -IdentityPoolUid ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['IdentityPoolUid']]) -Count $machinesToCreate
198 |
199 | if ($adAccounts.SuccessfulAccountsCount -le 0)
200 | {
201 | Write-Log "Failed to create new accounts using Identity Pool [$($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['IdentityPoolUid']])]." -DgName $DeliveryGroup.Name
202 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
203 | return
204 | }
205 |
206 | if ($adAccounts.FailedAccountsCount -gt 0)
207 | {
208 | Write-Log "Failed to create [$($adAccounts.FailedAccountsCount)] accounts. Accounts: [$($adAccounts.FailedAccounts)]" -DgName $DeliveryGroup.Name
209 | }
210 |
211 | $provTask = New-ProvVM -ProvisioningSchemeUid ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['ProvSchemeUid']]) -ADAccountName $adAccounts.SuccessfulAccounts -RunAsynchronously
212 |
213 | Write-Log "Began provisioning of [$($adAccounts.SuccessfulAccountsCount)] machines to [$($DeliveryGroup.Name)]. Monitoring task [$($provTask.Guid)]." -DgName $DeliveryGroup.Name
214 | $map = @{ `
215 | ($AutoscaleMetadataNames['ProvTask']) = [string]($provTask.Guid); `
216 | ($AutoscaleMetadataNames['Actions']) = [string]($adAccounts.SuccessfulAccountsCount); `
217 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::MonitorProvision); `
218 | }
219 |
220 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
221 | }
222 |
223 | function Watch-MachineDeployment
224 | {
225 | param([object]$DeliveryGroup)
226 |
227 | $provTaskId = ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['ProvTask']])
228 | $provTask = Get-ProvTask -TaskId $provTaskId
229 |
230 | # Sanity Check that the task we're monitoring is creating new machines
231 | if ($provTask.Type -ne "NewVirtualMachine")
232 | {
233 | Write-Log "Assertion failed: Task type [$($provTask.Type)] is not expected for machine deployment." -DgName $DeliveryGroup.Name
234 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
235 | return
236 | }
237 |
238 | if ($provTask.Active -eq $true)
239 | {
240 | # Provisioning is on-going
241 | return
242 | }
243 |
244 | Write-Log "Provisioning task [$provTaskId] is complete. [$($provTask.VirtualMachinesCreatedCount)] created. [$($provTask.VirtualMachinesCreationFailedCount)] failed to create." -DgName $DeliveryGroup.Name
245 |
246 | if ($provTask.VirtualMachinesCreatedCount -le 0)
247 | {
248 | Write-Log "Failed to create any machines. Error [$($provTask.FailedVirtualMachines.Status)]" -DgName $DeliveryGroup.Name
249 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
250 | return
251 | }
252 |
253 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::AddMachines)
254 | }
255 |
256 | function Add-MachinesToDesktopGroup
257 | {
258 | param([object]$DeliveryGroup)
259 |
260 | # Get Machines Created by ProvTask
261 | $provTaskId = ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['ProvTask']])
262 | $provTask = Get-ProvTask -TaskId $provTaskId
263 |
264 | # Add machines to catalog
265 | $catalog = Get-BrokerCatalog -Name ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['MachineCatalogName']])
266 | $machines = @()
267 | $provTask.CreatedVirtualMachines | ForEach-Object -Process { $machines += New-BrokerMachine -MachineName $_.VmName -CatalogUid $catalog.Uid}
268 |
269 | # Add tag to created machines and add them to the delivery group
270 | $machines | Add-BrokerTag ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['Tag']])
271 | $machines | Add-BrokerMachine -DesktopGroup $DeliveryGroup
272 |
273 | Write-Log "Added [$($machines.Count)] machines to [$($DeliveryGroup.Name)]." -DgName $DeliveryGroup.Name
274 |
275 | $map = @{ `
276 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::MonitorUsage); `
277 | ($AutoscaleMetadataNames['LastUpdateTime']) = (Get-CurrentTimeString); `
278 | }
279 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
280 | }
281 |
282 | function Remove-MachinesFromDesktopGroup
283 | {
284 | param([object]$DeliveryGroup)
285 |
286 | $machines = @(Get-BrokerMachine -Tag ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['Tag']]) `
287 | -DesktopGroupUid $DeliveryGroup.Uid `
288 | -SessionCount 0 `
289 | -Property MachineName,HostedMachineName)
290 | if ($machines.Count -le 0)
291 | {
292 | # No machines can be removed
293 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
294 | return
295 | }
296 | # Remove from Delivery Group
297 | $machines | Remove-BrokerMachine -DesktopGroup $DeliveryGroup
298 | # Remove from the Broker database
299 | $machines | Remove-BrokerMachine
300 |
301 | # Remove from Hypervisor and MCS database
302 | $machineNames = $machines | ForEach-Object {$_.HostedMachineName}
303 | $provTask = Remove-ProvVM -ProvisioningSchemeUid ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['ProvSchemeUid']]) -VMName $machineNames -RunAsynchronously
304 |
305 | Write-Log "Removing [$($machines.Count)] machines from [$($DeliveryGroup.Name)]. Monitoring task [$($provTask.Guid)]" -DgName $DeliveryGroup.Name
306 | $map = @{ `
307 | ($AutoscaleMetadataNames['ProvTask']) = [string]($provTask.Guid); `
308 | ($AutoscaleMetadataNames['Actions']) = [string]($machines.Count); `
309 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::MonitorDeleteMachines); `
310 | ($AutoscaleMetadataNames['LastUpdateTime']) = (Get-CurrentTimeString); `
311 | }
312 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
313 | }
314 |
315 | function Watch-MachineDeletion
316 | {
317 | param([object]$DeliveryGroup)
318 |
319 | $provTaskId = ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['ProvTask']])
320 | $provTask = Get-ProvTask -TaskId $provTaskId
321 |
322 | # Sanity Check that the task we're monitoring is deleting machines
323 | if ($provTask.Type -ne "RemoveVirtualMachine")
324 | {
325 | Write-Log "Assertion failed: Task type [$($provTask.Type)] is not expected for machine deletion." -DgName $DeliveryGroup.Name
326 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
327 | return
328 | }
329 |
330 | if ($provTask.Active -eq $true)
331 | {
332 | # Machine removal is on-going
333 | return
334 | }
335 |
336 | Write-Log "Machine deletion task [$provTaskId] is [$($provTask.TaskState)]." -DgName $DeliveryGroup.Name
337 |
338 | $adAccounts = $provTask.RemovedVirtualMachines | ForEach-Object {$_.ADAccountName}
339 | if ($adAccounts.Count -gt 0)
340 | {
341 | $result = Remove-AcctADAccount `
342 | -IdentityPoolUid ($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['IdentityPoolUid']]) `
343 | -ADAccountName $adAccounts `
344 | -RemovalOption Delete
345 |
346 | if ($result.FailedAccountsCount -gt 0)
347 | {
348 | Write-Log "Failed to remove [$($result.FailedAccountsCount)] out of [$($adAccounts.Count)] accounts. Accounts: [$($result.FailedAccounts)]." -DgName $DeliveryGroup.Name
349 | }
350 | }
351 |
352 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['State'] -Value ([AutoscaleScriptState]::MonitorUsage)
353 | }
354 |
355 | # State Machine --------------------------------------------------------------
356 | function Initialize-StateMachine
357 | {
358 | param([object]$DeliveryGroup)
359 |
360 | # Validate Incoming Parameters
361 | if ((Confirm-IncomingParameters $DeliveryGroup) -eq $false)
362 | {
363 | throw "Invalid parameters used to initialize autoscale machine creation."
364 | }
365 |
366 | $catalog = Get-BrokerCatalog -Name $MachineCatalogName -Property ProvisioningSchemeId
367 | $provScheme = Get-ProvScheme -ProvisioningSchemeUid ($catalog.ProvisioningSchemeId)
368 |
369 | # Add Initial MetaData to Delivery Group
370 | $map = @{ `
371 | ($AutoscaleMetadataNames['Tag']) = ($ScriptTag); `
372 | ($AutoscaleMetadataNames['CleanExit']) = "False"; `
373 | ($AutoscaleMetadataNames['State']) = [string]([AutoscaleScriptState]::MonitorUsage); `
374 | ($AutoscaleMetadataNames['MachineCatalogName']) = ($MachineCatalogName); `
375 | ($AutoscaleMetadataNames['IdentityPoolUid']) = [string]($provScheme.IdentityPoolUid); `
376 | ($AutoscaleMetadataNames['ProvSchemeUid']) = [string]($provScheme.ProvisioningSchemeUid); `
377 | }
378 |
379 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
380 | Edit-Watermarks $DeliveryGroup
381 |
382 | # Create the script tag, ignoring an error if it has been created before
383 | New-BrokerTag ($ScriptTag) -ErrorAction SilentlyContinue | Out-Null
384 |
385 | return [AutoscaleScriptState]::MonitorUsage
386 | }
387 |
388 | function Resume-AutoscaleStateMachine
389 | {
390 | param(
391 | [AutoscaleScriptState]$CurrentState,
392 | [object]$DeliveryGroup
393 | )
394 |
395 | Write-Log "Entering State: [$CurrentState] and the last run was successful: [$($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['CleanExit']])]." `
396 | -Trivial $true `
397 | -DgName $DeliveryGroup.Name
398 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['CleanExit'] -Value $false
399 |
400 | switch ($CurrentState)
401 | {
402 | MonitorUsage { Watch-AutoscaleLoadBalance $DeliveryGroup; break }
403 | ProvisionMachines { Publish-Machines $DeliveryGroup; break }
404 | MonitorProvision { Watch-MachineDeployment $DeliveryGroup; break }
405 | AddMachines { Add-MachinesToDesktopGroup $DeliveryGroup; break }
406 | RemoveMachines { Remove-MachinesFromDesktopGroup $DeliveryGroup; break }
407 | MonitorDeleteMachines { Watch-MachineDeletion $DeliveryGroup; break }
408 | Default { Write-Log "No action taken for [$($CurrentState)] state" -DgName $DeliveryGroup.Name }
409 | }
410 |
411 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['CleanExit'] -Value $true
412 | }
413 |
414 | # ============================================================================
415 | # Helper Functions
416 | # ============================================================================
417 |
418 | function Measure-DeliveryGroupLoad
419 | {
420 | param([object]$DeliveryGroup)
421 |
422 | if ($DeliveryGroup.SessionSupport -eq 'SingleSession')
423 | {
424 | return ($DeliveryGroup.Sessions * 100 / $DeliveryGroup.TotalDesktops)
425 | }
426 |
427 | $machineLoadArray = Get-BrokerMachine -DesktopGroupUid $DeliveryGroup.Uid -Property LoadIndex
428 | $totalLoad = $machineLoadArray | ForEach-Object -Begin { $total = 0 } -Process { $total += $_.LoadIndex } -End { $total }
429 |
430 | # Maximum load index per machines is a hard-coded number, for more information see:
431 | # https://docs.citrix.com/en-us/citrix-virtual-apps-desktops-service/manage-deployment/autoscale.html
432 | $maxLoad = 10000 * $machineLoadArray.Count
433 |
434 | return ($totalLoad * 100 / $maxLoad)
435 | }
436 |
437 | function Measure-HighWatermarkDelta
438 | {
439 | param([object]$DeliveryGroup)
440 |
441 | $highWm = [int]($DeliveryGroup.MetadataMap[$AutoscaleMetadataNames['HighWatermark']])
442 |
443 | if ($DeliveryGroup.SessionSupport -eq 'SingleSession')
444 | {
445 | return [math]::Ceiling((100 * $DeliveryGroup.Sessions) / $highWm - $DeliveryGroup.TotalDesktops)
446 | }
447 |
448 | $machineLoadArray = Get-BrokerMachine -DesktopGroupUid $DeliveryGroup.Uid -Property LoadIndex
449 | $totalLoad = $machineLoadArray | ForEach-Object -Begin { $total = 0 } -Process { $total += $_.LoadIndex } -End { $total }
450 |
451 | # Maximum load index per machines is a hard-coded number, for more information see:
452 | # https://docs.citrix.com/en-us/citrix-virtual-apps-desktops-service/manage-deployment/autoscale.html
453 | return [math]::Ceiling(($totalLoad * 100 / 10000) / $highWm - $machineLoadArray.Count)
454 | }
455 |
456 | function Confirm-IncomingParameters
457 | {
458 | param([object]$DeliveryGroup)
459 | # Stop at any error
460 | $ErrorActionPreference = 'Stop'
461 | $dgName = $DeliveryGroup.Name
462 |
463 | # Set to default if LowWatermark\HighWatermark was not set
464 | if ($LowWatermark -le 0)
465 | {
466 | $script:LowWatermark = 15
467 | $script:HighWatermark = 80
468 | Write-Log "Assuming default values for watermarks [$LowWatermark : $HighWatermark]." -DgName $dgName
469 | }
470 |
471 | # Machine Catalog
472 | $catalog = Get-BrokerCatalog -Name $MachineCatalogName
473 | if ($catalog.MachinesArePhysical)
474 | {
475 | Write-Log "Given catalog [$MachineCatalogName] only supports physical machines" -DgName $dgName
476 | return $false
477 | }
478 |
479 | if ($catalog.ProvisioningType -ne "MCS")
480 | {
481 | Write-Log "Given catalog [$MachineCatalogName] is not set to use Machine Creation Services." -DgName $dgName
482 | return $false
483 | }
484 |
485 | if ($catalog.SessionSupport -ne $DeliveryGroup.SessionSupport)
486 | {
487 | Write-Log "Given catalog [$MachineCatalogName] has a different SessionSupport than the delivery group." -DgName $dgName
488 | return $false
489 | }
490 |
491 | if ($null -eq $catalog.ProvisioningSchemeId)
492 | {
493 | Write-Log "Given catalog [$MachineCatalogName] does not have an associated provisioning scheme." -DgName $dgName
494 | return $false;
495 | }
496 |
497 | # Provisioning Scheme
498 | $provScheme = Get-ProvScheme -ProvisioningSchemeUid ($catalog.ProvisioningSchemeId)
499 |
500 | if ($null -eq $provScheme.IdentityPoolUid)
501 | {
502 | Write-Log "Associated provisioning scheme [($provScheme.ProvisioningSchemeUid)] does not have an associated Identity Pool." -DgName $dgName
503 | return $false;
504 | }
505 |
506 | # Identity Pool
507 | $identityPool = Get-AcctIdentityPool -IdentityPoolUid ($provScheme.IdentityPoolUid)
508 | if ($null -eq $identityPool.NamingScheme)
509 | {
510 | Write-Log "Associated identity pool [$IdentityPoolUid] does not have a corresponding NamingScheme." -DgName $dgName
511 | return $false
512 | }
513 |
514 | # Script Tag
515 | $tagExists = @(Get-BrokerTag | Where-Object { $_.Name -eq $ScriptTag}).Count -gt 0
516 | if ($tagExists)
517 | {
518 | Write-Log "Warning... tag [$ScriptTag] is already in use, machines marked with this tag could cause the Autoscale Machine Creation script to delete more machines than it created." -DgName $dgName
519 | }
520 |
521 | return $true
522 | }
523 |
524 | function Edit-Watermarks
525 | {
526 | param([object]$DeliveryGroup)
527 |
528 | if ($LowWatermark -le 0)
529 | {
530 | throw "[$($DeliveryGroup.Name)]: HighWatermark cannot be set without also setting the LowWatermark"
531 | }
532 |
533 | if ($HighWatermark -le 0)
534 | {
535 | throw "[$($DeliveryGroup.Name)]: LowWatermark cannot be set without also setting the HighWatermark"
536 | }
537 |
538 | if ($LowWatermark -ge $HighWatermark)
539 | {
540 | throw "[$($DeliveryGroup.Name)]: LowWatermark [$LowWatermark] can not be set to a higher value than HighWatermark [$HighWatermark]"
541 | }
542 |
543 | # Update stored watermarks
544 | $map = @{ `
545 | ($AutoscaleMetadataNames['LowWatermark']) = [string]($LowWatermark); `
546 | ($AutoscaleMetadataNames['HighWatermark']) = [string]($HighWatermark); `
547 | }
548 |
549 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
550 | }
551 |
552 | function Edit-MaximumCreatedMachines
553 | {
554 | param([object]$DeliveryGroup)
555 |
556 | # Update Maximum Created Machines
557 | $map = @{ `
558 | ($AutoscaleMetadataNames['MaximumCreatedMachines']) = [string]($MaximumCreatedMachines); `
559 | }
560 |
561 | $DeliveryGroup | Set-BrokerDesktopGroupMetadata -Map $map
562 | }
563 |
564 | function Write-Log
565 | {
566 | param(
567 | [String]$Event,
568 | [String]$DgName = $null,
569 | [bool]$Trivial = $false
570 | )
571 |
572 | if ($DgName)
573 | {
574 | $message = "[$DgName]: $Event"
575 | } else {
576 | $message = "Common Log: $Event"
577 | }
578 |
579 | if ($Trivial)
580 | {
581 | # Trivial should not be printed (unless debug output is needed)
582 | Write-Debug $message
583 | return
584 | }
585 |
586 | if ($EventLogSource)
587 | {
588 | Write-EventLog -LogName Application -Source $EventLogSource -Message $message -EventId 1
589 | return
590 | }
591 |
592 | Write-Host $message
593 | }
594 |
595 | function Get-CurrentTimeString
596 | {
597 | return [string] (Get-Date -UFormat "%Y/%m/%d %H:%M:%S");
598 | }
599 |
600 | # ============================================================================
601 | # Main
602 | # ============================================================================
603 |
604 | # Stop whenever an error is hit
605 | $ErrorActionPreference = 'Stop'
606 | Add-PSSnapIn Citrix.*
607 |
608 | # Load XD Credentials Profile
609 | Set-XDCredentials -ProfileName $XdProfileName
610 |
611 | # Extract Delivery Groups
612 | $dgNames = $DeliveryGroupName.Split(';')
613 |
614 | ForEach ($name in $dgNames)
615 | {
616 | try {
617 | $dg = Get-BrokerDesktopGroup -Name $name
618 | if ($null -eq $dg)
619 | {
620 | Write-Log "Could not retrieve delivery group with name [$name]"
621 | continue
622 | }
623 |
624 | if ($dg -is [System.Array])
625 | {
626 | Write-Log "Cannot use [$name] (wildcards\empty) to retrieve more than one desktop group."
627 | continue
628 | }
629 |
630 | $state = $dg.MetadataMap[$AutoscaleMetadataNames['State']]
631 | if ($null -eq $state)
632 | {
633 | # Enforce restriction (Initialization\Update of Delivery Group can only occur if there is exactly one delivery group)
634 | if ($dgNames.Length -ne 1)
635 | {
636 | Write-Log "Cannot update the watermarks or Machine Catalogs for more than one delivery group at a time"
637 | break
638 | }
639 |
640 | $state = Initialize-StateMachine $dg
641 |
642 | # Reload Delivery Group after initialization
643 | $dg = Get-BrokerDesktopGroup -Uid $dg.Uid
644 | }
645 | else
646 | {
647 | $state = [AutoscaleScriptState]$state
648 | }
649 |
650 | if (($LowWatermark -ge 0) -or ($HighWatermark -ge 0))
651 | {
652 | # Enforce restriction (Initialization\Update of Delivery Group can only occur if there is exactly one delivery group)
653 | if ($dgNames.Length -ne 1)
654 | {
655 | Write-Log "Cannot update the watermarks or Machine Catalogs for more than one delivery group at a time"
656 | break
657 | }
658 |
659 | # Update the watermarks in the delivery group
660 | Edit-Watermarks $dg
661 |
662 | # Reload Delivery Group after editing watermarks
663 | $dg = Get-BrokerDesktopGroup -Uid $dg.Uid
664 | }
665 |
666 | if ($MaximumCreatedMachines -ge 0)
667 | {
668 | # Enforce restriction (Initialization\Update of Delivery Group can only occur if there is exactly one delivery group)
669 | if ($dgNames.Length -ne 1)
670 | {
671 | Write-Log "Cannot update the MaximumCreatedMachines more than one delivery group at a time"
672 | break
673 | }
674 |
675 | # Update MaximumCreatedMachines
676 | Edit-MaximumCreatedMachines $dg
677 |
678 | # Reload Delivery Group after editing MaximumCreatedMachines
679 | $dg = Get-BrokerDesktopGroup -Uid $dg.Uid
680 | }
681 |
682 | if ($EventLogSource)
683 | {
684 | # Update EventLogSource
685 | $dg | Set-BrokerDesktopGroupMetadata -Name $AutoscaleMetadataNames['EventLogSource'] -Value $EventLogSource
686 | }
687 | else
688 | {
689 | # Load EventLogSource
690 | $EventLogSource = $dg.MetadataMap[$AutoscaleMetadataNames['EventLogSource']]
691 | }
692 |
693 | Resume-AutoscaleStateMachine $state $dg
694 | } catch
695 | {
696 | Write-Log "While processing [$name] encountered error: $_"
697 | }
698 | }
699 |
--------------------------------------------------------------------------------
/XAXD/AutoscaleMcs/LICENSE.MD:
--------------------------------------------------------------------------------
1 | Copyright 2019 Citrix Systems, Inc.
2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/XAXD/AutoscaleMcs/README.MD:
--------------------------------------------------------------------------------
1 | # Autoscale Machine Creation Services
2 |
3 | This script is intended to autoscale a delivery group based on load using
4 | Machine Creation Services.
5 |
6 | ## Getting Started
7 |
8 | Official documentation of this script is available here: https://docs.citrix.com/en-us/citrix-virtual-apps-desktops-service/manage-deployment/autoscale.html#dynamically-provision-machines-with-autoscale
9 |
10 |
11 | A detailed description is also available within the script itself
12 |
13 | ```powershell
14 | Get-Help .\Invoke-AutoscaleMachineCreation.ps1
15 | ```
16 |
17 | ## Prerequisites
18 |
19 | Requires the Citrix Remote PowerShell SDK
20 |
21 | Must enable the running of 3rd party scripts within PowerShell (Set-ExecutionPolicy)
22 |
23 | Must use an account that is both a domain administator (or other user that is able to create machine accounts)
24 |
25 | Other pre-requisites are detailed in the help text.
26 |
27 | ## Built With
28 |
29 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
30 |
31 | ## NEW IN THIS VERSION (1.0.1)
32 |
33 | Ability to limit the maximum number of machines created by this script
34 |
35 | ## EXAMPLES
36 |
37 | This script should be run from a machine that can access the Internet and is on the same
38 | domain as the machines that will be created\added.
39 |
40 | .\Invoke-AutoscaleMachineCreation.ps1 -DeliveryGroup -XdProfileName
41 |
42 | ## Versioning & Authors
43 |
44 | VERSION
45 | 1.0.1
46 |
47 | DATE MODIFIED
48 | 11/7/2019
49 |
50 | AUTHOR
51 | Cristopher Jimenez, Engineering
--------------------------------------------------------------------------------
/XAXD/SiteFeatureDetection/README.MD:
--------------------------------------------------------------------------------
1 | # XDFeatureDetection
2 |
3 | This script is intended to detect the usage of key features within XenApp/XenDesktop sites.
4 |
5 | ## Getting Started
6 |
7 | Simply execute the PowerShell script and output is returned on the feature usage.
8 | From a powershell command prompt run the following
9 |
10 | ```powershell
11 | ./XDFeatureDetection.ps1
12 | ```
13 |
14 | ## Prerequisites
15 |
16 | Requires the built-in Citrix PowerShell snap-ins on the delivery controller
17 |
18 | Must enable the running of 3rd party scripts within PowerShell (Set-ExecutionPolicy)
19 |
20 | Must use an account that is both a local administrator and Citrix administrator
21 |
22 | ## Built With
23 |
24 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
25 |
26 | ## NEW IN THIS VERSION (1.1.0)
27 |
28 | Cleaned up formatting, adjusted logic, now including feature eligibility
29 |
30 | ## EXAMPLES
31 |
32 | This script should be run (as administrator) on a delivery controller in a XenApp/XenDesktop site.
33 |
34 | .\XDFeatureDetection.ps1
35 |
36 | ## Versioning & Authors
37 |
38 | VERSION
39 | 1.1.0
40 |
41 | DATE MODIFIED
42 | 11/30/2017
43 |
44 | AUTHOR
45 | Allen Furmanski, Citrix Product Marketing
46 |
--------------------------------------------------------------------------------
/XAXD/SiteFeatureDetection/XDFeatureDetection.ps1:
--------------------------------------------------------------------------------
1 | ###################################
2 | #######
3 | #######
4 | ####### XA/XD Feature Detection
5 | #######
6 | ####### Detects key features in
7 | ####### use within a site along
8 | ####### with entitlement info
9 | #######
10 | ####### Version 1.1.0
11 | #######
12 | ####### 11-30-2017
13 | #######
14 | #######
15 | ###################################
16 |
17 | # Load Citrix snap-ins
18 | asnp citrix*
19 |
20 | # Get overall site configuration
21 | $siteConfig = Get-ConfigSite
22 |
23 |
24 | # Detect if MCS is in use
25 | function CheckMCSinUse ()
26 | {
27 | $GetBC = Get-BrokerCatalog
28 | $TotalMCS = ($GetBC.provisioningtype -eq 'MCS').count
29 |
30 | If ($TotalMCS -gt 0)
31 | {
32 | # One or more MCS catalogs were found so return true
33 | return $true
34 | }
35 | else
36 | {
37 | return $false
38 | }
39 | }
40 |
41 |
42 | # Detect if PVS is in use
43 | function CheckPVSinUse ()
44 | {
45 | $GetBC = Get-BrokerCatalog
46 | $TotalPVS = ($GetBC.provisioningtype -eq 'PVS').count
47 |
48 | If ($TotalPVS -gt 0)
49 | {
50 | # One or more PVS catalogs were found so return true
51 | return $true
52 | }
53 | else
54 | {
55 | return $false
56 | }
57 | }
58 |
59 | # Check if XenDesktop is the product in use
60 | function CheckXenDesktop ()
61 | {
62 | if($siteConfig.ProductCode -ne 'XDT')
63 | {
64 | return $false
65 | }
66 | else
67 | {
68 | return $true
69 | }
70 |
71 | }
72 |
73 |
74 | # Check if not using XD VDI Edition
75 | function CheckNotVDIEdition ()
76 | {
77 | if($siteConfig.ProductEdition -ne 'VDI')
78 | {
79 | return $true
80 | }
81 | else
82 | {
83 | return $false
84 | }
85 |
86 | }
87 |
88 | # Check if eligible for platinum-level features
89 | function CheckPlatinumFeature ()
90 | {
91 | if($siteConfig.ProductEdition -ne 'PLT')
92 | {
93 | return $false
94 | }
95 | else
96 | {
97 | return $true
98 | }
99 |
100 | }
101 |
102 | # Check if eligible for enterprise-level features and higher
103 | function CheckEnterpriseFeature ()
104 | {
105 | if(($siteConfig.ProductEdition -ne 'PLT')-and($siteConfig.ProductEdition -ne 'ENT'))
106 | {
107 | return $false
108 | }
109 | else
110 | {
111 | return $true
112 | }
113 |
114 | }
115 |
116 | # Check if eligible for XD enterprise-level features and higher
117 | function CheckXDEnterpriseFeature ()
118 | {
119 | if(($siteConfig.Code -ne 'XDT')-and($siteConfig.ProductEdition -ne 'PLT')-and($siteConfig.ProductEdition -ne 'ENT'))
120 | {
121 | return $false
122 | }
123 | else
124 | {
125 | return $true
126 | }
127 |
128 | }
129 |
130 | # Detect if delegated administration is in use
131 | function CheckDelegatedAdmininUse ()
132 | {
133 | $GetAdmins = Get-AdminAdministrator
134 |
135 | If ($GetAdmins.count -gt 1)
136 | {
137 | # Multiple administrator accounts exist
138 | return $true
139 | }
140 | else
141 | {
142 | return $false
143 | }
144 | }
145 |
146 | # Detect if zones are in use
147 | function CheckZonesinUse ()
148 | {
149 | $Zones = Get-ConfigZone
150 |
151 | If ($Zones.count -gt 1)
152 | {
153 | # More than one zone exists
154 | return $true
155 | }
156 | else
157 | {
158 | return $false
159 | }
160 | }
161 |
162 | # Detect Remote PC Access in use
163 | function CheckRemotePCinUse ()
164 | {
165 | $GetBC = Get-BrokerCatalog
166 | $TotalRemotePC = ($GetBC.IsRemotePC -eq 'True').count
167 |
168 | If ($TotalRemotePC -gt 0)
169 | {
170 | # One or more Remote PC catalogs were found
171 | return $true
172 | }
173 | else
174 | {
175 | return $false
176 | }
177 | }
178 |
179 | # Detect cloud connections in use
180 | function CheckCloudConnectioninUse ()
181 | {
182 | cd xdhyp:\connections
183 | $HostingConnections = dir
184 |
185 | # Only proceed if there are actual hosting connections
186 | If ($HostingConnections.count -gt 0)
187 | {
188 | $TotalCloudConnections = ($HostingConnections.PluginId -eq 'AzureRmFactory').count + ($HostingConnections.PluginId -eq 'AWSMachineManagerFactory').count
189 |
190 | If ($TotalCloudConnections -gt 0)
191 | {
192 | # One or more cloud connections exist
193 | return $true
194 | }
195 | else
196 | {
197 | return $false
198 | }
199 | }
200 | else
201 | {
202 | return $false
203 | }
204 | }
205 |
206 | # Detect SCOM Packs in use
207 | function CheckSCOMPacksinUse ()
208 | {
209 |
210 | # See if one or more Citrix SCOM components are installed on the controller
211 | if((Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName | where-object {$_.DisplayName -like 'Citrix SCOM*'} | measure | Select-Object -Exp Count) -gt 0)
212 | {
213 | return $true
214 | }
215 | else
216 | {
217 | return $false
218 | }
219 |
220 | }
221 |
222 | # Detect VDI in use
223 | function CheckVDIinUse ()
224 | {
225 | $GetBC = Get-BrokerCatalog
226 | $TotalVDI = ($GetBC.SessionSupport -eq 'SingleSession').count
227 |
228 | If ($TotalVDI -gt 0)
229 | {
230 | # One or more VDI catalogs found
231 | return $true
232 | }
233 | else
234 | {
235 | return $false
236 | }
237 | }
238 |
239 | # Check published apps in use
240 | function CheckAppsinUse ()
241 | {
242 | $GetApps = Get-BrokerApplication
243 | $TotalApps = $GetApps.count
244 |
245 | If ($TotalApps -gt 0)
246 | {
247 | # One or more published apps exist
248 | return $true
249 | }
250 | else
251 | {
252 | return $false
253 | }
254 | }
255 |
256 | # High-level site info
257 | Write-Host "Site Name:`t",$siteConfig.SiteName
258 | if($siteConfig.ProductCode -eq 'XDT')
259 | {
260 | # XDT code is XenDesktop
261 | Write-Host "Product:`t XenDesktop"
262 | }
263 | else
264 | {
265 | # Other code of MPS is XenApp
266 | Write-Host "Product:`t XenApp"
267 | }
268 | Write-Host "Edition:`t",$siteConfig.ProductEdition
269 | Write-Host "Version:`t",$siteConfig.ProductVersion
270 | Write-Host ""
271 | Write-Host ""
272 |
273 | Write-Host "Machine Creation Services (MCS)"
274 | Write-Host "--------------------------------"
275 | Write-Host "In Use?:`t`t`t",$(CheckMCSinUse)
276 | Write-Host "Edition:`t`t`t All"
277 | Write-Host "Entitled?:`t`t`t True"
278 | Write-Host ""
279 | Write-Host ""
280 |
281 |
282 | Write-Host "Provisioning Services (PVS)"
283 | Write-Host "--------------------------------"
284 | Write-Host "In Use?:`t`t`t",$(CheckPVSinUse)
285 | Write-Host "Edition:`t`t`t ENT,PLT"
286 | Write-Host "Entitled?:`t`t`t",$(CheckEnterpriseFeature)
287 | Write-Host ""
288 | Write-Host ""
289 |
290 |
291 | Write-Host "Delegated Administration"
292 | Write-Host "--------------------------------"
293 | Write-Host "In Use?:`t`t`t",$(CheckDelegatedAdmininUse)
294 | Write-Host "Edition:`t`t`t ENT,PLT"
295 | Write-Host "Entitled?:`t`t`t",$(CheckEnterpriseFeature)
296 | Write-Host ""
297 | Write-Host ""
298 |
299 |
300 | Write-Host "Zones"
301 | Write-Host "--------------------------------"
302 | Write-Host "In Use?:`t`t`t",$(CheckZonesinUse)
303 | Write-Host "Edition:`t`t`t All"
304 | Write-Host "Entitled?:`t`t`t True"
305 | Write-Host ""
306 | Write-Host ""
307 |
308 |
309 | Write-Host "Remote PC Access"
310 | Write-Host "--------------------------------"
311 | Write-Host "In Use?:`t`t`t",$(CheckRemotePCinUse)
312 | Write-Host "Edition:`t`t`t XD ENT,PLT"
313 | Write-Host "Entitled?:`t`t`t",$(CheckXDEnterpriseFeature)
314 | Write-Host ""
315 | Write-Host ""
316 |
317 |
318 | Write-Host "Cloud Connections"
319 | Write-Host "--------------------------------"
320 | Write-Host "In Use?:`t`t`t",$(CheckCloudConnectioninUse)
321 | Write-Host "Edition:`t`t`t ENT,PLT"
322 | Write-Host "Entitled?:`t`t`t",$(CheckEnterpriseFeature)
323 | Write-Host ""
324 | Write-Host ""
325 |
326 |
327 | Write-Host "SCOM Management Packs"
328 | Write-Host "--------------------------------"
329 | Write-Host "In Use?:`t`t`t",$(CheckSCOMPacksinUse)
330 | Write-Host "Edition:`t`t`t PLT"
331 | Write-Host "Entitled?:`t`t`t",$(CheckPlatinumFeature)
332 | Write-Host ""
333 | Write-Host ""
334 |
335 |
336 | Write-Host "VDI Desktops"
337 | Write-Host "--------------------------------"
338 | Write-Host "In Use?:`t`t`t",$(CheckVDIinUse)
339 | Write-Host "Edition:`t`t`t XD VDI,ENT,PLT"
340 | Write-Host "Entitled?:`t`t`t",$(CheckXenDesktop)
341 | Write-Host ""
342 | Write-Host ""
343 |
344 |
345 | Write-Host "Published Applications"
346 | Write-Host "--------------------------------"
347 | Write-Host "In Use?:`t`t`t",$(CheckAppsinUse)
348 | Write-Host "Edition:`t`t`t All Except XD VDI"
349 | Write-Host "Entitled?:`t`t`t",$(CheckNotVDIEdition)
350 | Write-Host ""
351 | Write-Host ""
352 |
--------------------------------------------------------------------------------
/XAXD/VMsInUnknownPowerState/Get-VmInUnknownPowerState.ps1:
--------------------------------------------------------------------------------
1 | <#
2 | .SYNOPSIS
3 | Diagnosing and (semi-) automatically resolving VMs on Citrix Virtual Apps and Desktops (CVAD) in an unknown power state.
4 | .DESCRIPTION
5 | Get-ProvVmInUnknownPowerState diagnoses the VMs on CVAD in an unknown power state, reports the causes, and (semi-) automatically fixes the causes.
6 | Specifically, Get-ProvVmInUnknownPowerState utilizes the names of VMs on CVAD to find the corresponding VMs on hypervisors and check three scenarios below that can make the power state of the VMs unknown.
7 | Scenario 1: the state of the hypervisor connection is unavailable. such as connection credentials being invalid.
8 | Scenario 2: the VMs on the hypervisor are deleted.
9 | Scenario 3: the IDs of VMs on CVAD mismatches the corresponding IDs of the VM on hypervisors.
10 |
11 | In case of scenario 1 and 2, Get-ProvVmInUnknownPowerState reports the causes of the unknown power state and provide suggestions to help admins fix the issues, such as correcting the credentials or deleting the VMs on CVAD.
12 | In the case of scenario 3, Get-ProvVmInUnknownPowerState reports the causes and suggestions.
13 | If the "-Fix" parameter is specified, Get-ProvVmInUnknownPowerState fixes the causes of scenario 3 automatically.
14 | The scenario 1, 2, and 3 are exclusive. If the connection is unavailable, it is not possible to access the hypervisor; consequently, check whether the corresponding VM on the hypervisor is deleted or the IDs are matched.
15 | .INPUTS
16 | Get-ProvVmInUnknownPowerState can take a hypervisor connection name, a broker catalog name, and/or "-Fix" as input.
17 | .OUTPUTS
18 | 1. If either a hypervisor connection name or a broker catalog is given, then Get-ProvVmInUnknownPowerState reports the VMs in an unknown power state associated with the hypervisor connection name or the broker catalog.
19 | 2. If both a hypervisor connection name and a broker catalog are given, then Get-ProvVmInUnknownPowerState reports the VMs associated with the broker catalog, because the VMs on the catalog is a subset of the VMs on the hypervisor connection
20 | 3. Without the "-Fix" parameter, the admin can get the report that includes the list of the VMs with the unknown power states together with the causes and suggestions.
21 | 4. With the parameter "-Fix", the admin can get the report, and the script automatically resolved the issue of scenario 3 by updating the IDs of VMs.
22 | .PARAMETER HypervisorConnectionName
23 | The name of the hypervisor connection. Report the VMs associated with the hypervisor connection.
24 | .PARAMETER BrokerCatalogName
25 | The name of the broker catalogue name. Report the VMs associated with the catalog.
26 | .PARAMETER Fix
27 | Update the UUIDs of VMs automatically to resolve the mismatched UUID issue.
28 | .NOTES
29 | Version : 1.0.1
30 | Author : Citrix Systems, Inc.
31 | .EXAMPLE
32 | Get-ProvVmInUnknownPowerState
33 | .EXAMPLE
34 | Get-ProvVmInUnknownPowerState -Fix
35 | .EXAMPLE
36 | Get-ProvVmInUnknownPowerState -HypervisorConnectionName "ExampleConnectionName" -Fix
37 | #>
38 |
39 | Param(
40 | [Parameter(Mandatory=$false)]
41 | [string]$HypervisorConnectionName,
42 | [Parameter(Mandatory=$false)]
43 | [string]$BrokerCatalogName,
44 | [Parameter(Mandatory=$false)]
45 | [switch]$Fix
46 | )
47 |
48 | # Add XDHyp
49 | Add-PSSnapin -Name "Citrix.Broker.Admin.V2","Citrix.Host.Admin.V2"
50 |
51 | # Get unavailable hypervisor connections.
52 | function Get-ConnectionUnavailable {
53 | $connectionsUnavailable = [System.Collections.ArrayList]::new()
54 |
55 | # If a hypervisor is specified an an input, check the hypervisor connection associated the input.
56 | if ($BrokerCatalogName) {
57 | try {
58 | $connName = (Get-BrokerMachine -CatalogName $BrokerCatalogName -Property @("HypervisorConnectionName") -MaxRecordCount 1).HypervisorConnectionName
59 | $connection = Get-BrokerHypervisorConnection -Name $connName -Property @("Name", "HypHypervisorConnectionUid", "State", "FaultState")
60 | if ($connection.State -eq "Unavailable") {
61 | return $connection
62 | }
63 | return $null
64 | }
65 | catch {
66 | Write-Error -Message "Failed while getting connections unavailables. Check the input broker catalog $($BrokerCatalogName) and the associated connection." -ErrorAction Stop
67 | }
68 | }
69 | # If a catalog is specified an an input, check the hypervisor connection associated the input.
70 | elseif ($HypervisorConnectionName) {
71 | try {
72 | $connection = Get-BrokerHypervisorConnection -Name $HypervisorConnectionName -Property @("Name", "HypHypervisorConnectionUid", "State", "FaultState")
73 | if ($connection.State -eq "Unavailable") {
74 | return $connection
75 | }
76 | return $null
77 | }
78 | catch {
79 | Write-Error -Message "Failed while getting connections unavailables. Check the input hypervisor connection $($HypervisorConnectionName)." -ErrorAction Stop
80 | }
81 | }
82 | # else check all hypervisor connections
83 | else {
84 | try {
85 | # Get the total connection count.
86 | Get-BrokerHypervisorConnection -State "Unavailable" -ReturnTotalRecordCount -MaxRecordCount 0 -ErrorVariable resultsCount -ErrorAction SilentlyContinue
87 | $totalCount = $resultsCount[0].TotalAvailableResultCount
88 | $currentCount = 0
89 | $skipCount = 0
90 |
91 | # Get the hypervisor connections unavailable. Each iteration can receive maximum 250 records only.
92 | while ($skipCount -lt $totalCount) {
93 | # Get 250 connections unavailable.
94 | $partialConnections = Get-BrokerHypervisorConnection -MaxRecordCount 250 -Skip $skipCount -State "Unavailable" -Property @("Name", "HypHypervisorConnectionUid", "FaultState")
95 |
96 | # Add the hypervisor connections
97 | [void]$connectionsUnavailable.Add($partialConnections)
98 |
99 | # Increase the skip count to check the remaining connections.
100 | $skipCount += 250
101 |
102 | # Count the connections checked.
103 | $currentCount = $partialConnections.Count
104 | Write-Host "Checked $($currentCount) unavailable connections out of $($totalCount) unavailable connections ($($currentCount / $totalCount * 100))%."
105 | }
106 |
107 | # return the total connections
108 | return $connectionsUnavailable
109 | }
110 | catch {
111 | Write-Error -Message "Failed while getting connections unavailables. Check Get-BrokerHypervisorConnection to get more than 250 records." -ErrorAction Stop
112 | }
113 | }
114 | }
115 |
116 |
117 | # Get the actual VMs on hypervisors.
118 | function Get-VmOnHypervisor {
119 | Param ($UnavailableConnection)
120 |
121 | # Get VMs on the Hypervisor. If the connection is unavailable, ignore the connection.
122 | $vmList = [System.Collections.ArrayList]::new()
123 | $previousCount = 0
124 |
125 | try {
126 | Get-ChildItem -Path @('XDHyp:\Connections') | Where-Object {$UnavailableConnection.Name -NotContains $_.HypervisorConnectionName} | ForEach-Object {
127 | # If a catalog is specified an an input, get the VMs associated to the catalog only.
128 | if ($BrokerCatalogName) {
129 | try {
130 | $connName = (Get-BrokerMachine -CatalogName $BrokerCatalogName -Property @("HypervisorConnectionName") -MaxRecordCount 1).HypervisorConnectionName
131 | if ($_.HypervisorConnectionName -eq $connName) {
132 | Write-Host "Checking the VMs on $($_.ConnectionTypeName), where the connection $($connName) connects to"
133 | Find-VMs -Path $_.PSPath -Connection $_.HypervisorConnectionName -VmList $vmList
134 | Write-Host "Checked $($vmList.Count - $previousCount) VMs using the connection $($_.HypervisorConnectionName)."
135 | }
136 | }
137 | catch {
138 | Write-Error -Message "Failed while getting VMs on XDHyp. Check the broker catalog $($BrokerCatalogName) and $($_.PSPath)." -ErrorAction Stop
139 | }
140 | }
141 | # If a hypervisor is specified an an input, get the VMs associated to the connection only.
142 | elseif ($HypervisorConnectionName) {
143 | try {
144 | # If the current connection equals to the input connection, then find the VMs on the connection.
145 | if ($_.HypervisorConnectionName -eq $HypervisorConnectionName) {
146 | Write-Host "Checking the VMs on $($_.ConnectionTypeName), where the connection $($_.HypervisorConnectionName) connects to"
147 | Find-VMs -Path $_.PSPath -Connection $_.HypervisorConnectionName -VmList $vmList
148 | Write-Host "Checked $($vmList.Count - $previousCount) VMs using the connection $($_.HypervisorConnectionName)."
149 | }
150 | }
151 | catch {
152 | Write-Error -Message "Failed while getting VMs on XDHyp. Check the hypervisor conection $($HypervisorConnectionName) and $($_.PSPath)." -ErrorAction Stop
153 | }
154 | }
155 | # Else, find VMs on all hypervisor connections.
156 | else {
157 | try {
158 | Write-Host "Checking the VMs using the connection $($_.HypervisorConnectionName)"
159 | $previousCount = $vmList.Count
160 | Find-VMs -Path $_.PSPath -Connection $_.HypervisorConnectionName -VmList $vmList
161 | Write-Host "Checked $($vmList.Count - $previousCount) VMs using the connection $($_.HypervisorConnectionName)."
162 | }
163 | catch {
164 | Write-Error -Message "Failed while getting VMs on XDHyp. Check $($_.PSPath)." -ErrorAction Stop
165 | }
166 | }
167 | }
168 | return $vmList
169 | }
170 | catch {
171 | Write-Error -Message "Failed while getting VMs on XDHyp." -ErrorAction Stop
172 | }
173 | }
174 |
175 | # Get the actual VMs on hypervisors.
176 | function Find-VMs {
177 | param ($Path, $ConnectionName, $VmList)
178 |
179 | try {
180 | Get-ChildItem -LiteralPath @($Path) -Force | ForEach-Object {
181 | # If the item is VM, then add it to the VM list
182 | if ($_.ObjectType -eq "Vm") {
183 | $vm = $_ | Select-Object FullName, Id, HypervisorConnectionName
184 | $vm.HypervisorConnectionName = $ConnectionName
185 | [void]$VmList.Add($vm)
186 | }
187 | # If the item contains sub-items, the travel the item as well
188 | if ( ($_.PSIsContainer -eq $true) -or ($_.IsContainer -eq $true)) {
189 | Find-VMs -Path $_.PSPath -Connection $ConnectionName -VmList $vmList
190 | }
191 | }
192 | }
193 | catch {
194 | Write-Error -Message "Failed while finding VMs on $($Path)." -ErrorAction Stop
195 | }
196 | }
197 |
198 | # Get the VMs on Broker in an Unknown Power State.
199 | function Get-VmOnBroker {
200 | $vmsUnknown = [System.Collections.ArrayList]::new()
201 |
202 | # Get the total number of VMs in an unknown state. If a catalog or a hypervisor connection is specified, then count the VMs associated to the input only.
203 | try {
204 | if ($BrokerCatalogName) {
205 | Get-BrokerMachine -PowerState "Unknown" -CatalogName $BrokerCatalogName -ReturnTotalRecordCount -MaxRecordCount 0 -ErrorVariable resultsCount -ErrorAction SilentlyContinue
206 | }
207 | elseif ($HypervisorConnectionName) {
208 | Get-BrokerMachine -PowerState "Unknown" -HypervisorConnectionName $HypervisorConnectionName -ReturnTotalRecordCount -MaxRecordCount 0 -ErrorVariable resultsCount -ErrorAction SilentlyContinue
209 | }
210 | else {
211 | Get-BrokerMachine -PowerState "Unknown" -ReturnTotalRecordCount -MaxRecordCount 0 -ErrorVariable resultsCount -ErrorAction SilentlyContinue
212 | }
213 | $totalCount = $resultsCount[0].TotalAvailableResultCount
214 | if ($totalCount -eq 0) {
215 | Write-Host "No VMs are in an unknown power state."
216 | exit
217 | }
218 | }
219 | catch {
220 | Write-Error -Message "Failed while getting the total number of VMs on CVAD in an unknown power state." -ErrorAction Stop
221 | }
222 |
223 | # Get the VMs with the unknown power state. Each iteration can receive maximum 250 records only.
224 | $currentCount = 0
225 | $skipCount = 0
226 |
227 | try {
228 | while ($skipCount -lt $totalCount) {
229 | # If a catalog name is specified in the input
230 | if ($BrokerCatalogName) {
231 | # Get 250 VMs in an unknown power state associated to the specified broker catalog.
232 | $partialVMs = Get-BrokerMachine -PowerState "Unknown" -MaxRecordCount 250 -Skip $skipCount -CatalogName $BrokerCatalogName -Property @("HostedMachineName", "HostedMachineId", "CatalogName", "CatalogUUID", "HypervisorConnectionName", "HypHypervisorConnectionUid")
233 | }
234 | # If a hypervisor name is specified in the input
235 | elseif ($HypervisorConnectionName) {
236 | # Get 250 VMs in an unknown power state associated to the specified hypervisor connection.
237 | $partialVMs = Get-BrokerMachine -PowerState "Unknown" -MaxRecordCount 250 -Skip $skipCount -HypervisorConnectionName $HypervisorConnectionName -Property @("HostedMachineName", "HostedMachineId", "CatalogName", "CatalogUUID", "HypervisorConnectionName", "HypHypervisorConnectionUid")
238 | }
239 | # If there is no input
240 | else {
241 | # Get 250 VMs in an unknown power state.
242 | $partialVMs = Get-BrokerMachine -PowerState "Unknown" -MaxRecordCount 250 -Skip $skipCount -Property @("HostedMachineName", "HostedMachineId", "CatalogName", "CatalogUUID", "HypervisorConnectionName", "HypHypervisorConnectionUid")
243 | }
244 |
245 | # Count the VMs loaded.
246 | $currentCount = $partialVMs.Count
247 | Write-Host "Checked $($currentCount) VMs out of $($totalCount) VMs ($($currentCount / $totalCount * 100)%)."
248 |
249 | # Add the VMs in an unknown power state
250 | $partialVMs = $partialVMs | Select-Object HostedMachineName, HostedMachineId, CatalogName, CatalogUUID, HypervisorConnectionName, HypHypervisorConnectionUid, Cause, Suggestion
251 | [void]$vmsUnknown.Add($partialVMs)
252 |
253 | # Increase the skip count to check the remaining VMs.
254 | $skipCount += 250
255 | }
256 |
257 | # return the total VMs
258 | return $vmsUnknown
259 | }
260 | catch {
261 | Write-Error -Message "Failed while getting partial VMs on CVAD in an unknown power state." -ErrorAction Stop
262 | }
263 | }
264 |
265 | # Main logic.
266 | function Get-VMsInUnknownPowerState {
267 | $result = [System.Collections.ArrayList]::new()
268 |
269 | # Get the hypervisor connections that have unavailable state
270 | Write-Host "1. Checking the states of hypervisor connections."
271 | $connectionsUnavailable = Get-ConnectionUnavailable
272 |
273 | # Get the VMs in an unknown power state.
274 | Write-Host "2. Checking the VMs on CVAD in an unknown power state."
275 | $vmsUnknown = Get-VmOnBroker
276 |
277 | # Get actual VMs on hypervisors
278 | Write-Host "3. Checking VMs on hypervisors corresponding to the VMs on CVAD."
279 | $vmsOnHyp = Get-VmOnHypervisor -UnavailableConnection $connectionsUnavailable
280 |
281 | # Iterate each VM in an Unknown Power State.
282 | Write-Host "4. Reporting (and Fixing) the VMs in an unknown power state."
283 |
284 | try {
285 | $vmsUnknown | ForEach-Object -Process {
286 | # Scenario 1: Broken Host Connections.
287 | # Get the host connection state that the VM is associated.
288 | $connection = $connectionsUnavailable | Where-Object HypHypervisorConnectionUid -eq $_.HypHypervisorConnectionUid
289 | if ($connection -ne $null) {
290 | $_.Cause = "The hypervisor connection is unavailable due to $($connection.FaultState)."
291 | $_.Suggestion = "Check the hypervisor connection named $($connection.Name)."
292 | }
293 | # Scenario 2: VMs are deleted.
294 | elseif ($vmsOnHyp.FullName -NotContains $($_.HostedMachineName + ".vm")) {
295 | $_.Cause = "The VM on the hypervisor is deleted."
296 | $_.Suggestion = "Check whether the VM still exists on the hypervisor and then update CVAD."
297 | }
298 | # Scenario 3: the id of the VM on CVAD is mismatched with the id of the corresponding VM on the hypervisor.
299 | elseif ($vmsOnHyp.FullName -Contains $($_.HostedMachineName + ".vm")) {
300 | $curVmOnHyp = $vmsOnHyp | Where-Object FullName -eq $($_.HostedMachineName + ".vm")
301 | if ($curVmOnHyp.Id -ne $_.HostedMachineId) {
302 | $_.Cause = "The ID of the VM on CVAD is mismatched with the ID of the corresponding VM on the hypervisor."
303 | # if -Fix parameter is given, then update the VM ID.
304 | if ($Fix) {
305 | try {
306 | Get-BrokerMachine -HostedMachineName $_.HostedMachineName | Set-BrokerMachine -HostedMachineId $curVmOnHyp.Id
307 | }
308 | catch {
309 | $_.Cause = "Failed to fix the mismatched IDs of VMs."
310 | $_.Suggestion = "Update the VM ID with $($curVmOnHyp.Id), then restart the broker service. `n option 1) To fix it automatiicaly, Get-MachineUnknown -Fix `n option 2) To fix it manually, Get-BrokerMachine -HostedMachineName ""$($_.HostedMachineName)"" | Set-BrokerMachine -HostedMachineId ""$($curVmOnHyp.Id)"""
311 | }
312 | $_.Suggestion = "The VM ID is updated with $($curVmOnHyp.Id), then restart the broker service."
313 | } else {
314 | # If -Fix parameter is not given, then provide a way to fix the ID manually
315 | $_.Suggestion = "Update VM ID with $($curVmOnHyp.Id), then restart the broker service. `n option 1) To fix it automatiicaly, Get-ProvVmInUnknownPowerState -Fix `n option 2) To fix it manually, Get-BrokerMachine -HostedMachineName ""$($_.HostedMachineName)"" | `nSet-BrokerMachine -HostedMachineId ""$($curVmOnHyp.Id)"""
316 | }
317 | }
318 | else {
319 | # After fixing the mismatche ID issue, but the broker service is not restarted properly yet.
320 | $_.Cause = "Please make sure the broker service is restarted properly."
321 | $_.Suggestion = "Restart the broker service or wait for the broker service to be restarted."
322 | }
323 | }
324 | # Exeptional cases. The broker service is not restarted properly yet.
325 | else {
326 | $_.Cause = "Please make sure the broker service is restarted properly."
327 | $_.Suggestion = "Restart the broker service or wait for the broker service to be restarted."
328 | }
329 | [void]$result.Add($_)
330 | }
331 | }
332 | catch {
333 | Write-Error -Message "Failed while updating the cause and suggestion for $($_.HostedMachineName)." -ErrorAction Stop
334 | }
335 |
336 | return $result
337 | }
338 |
339 | # Begining of Get-ProvVmInUnknownPowerState.
340 | $result = Get-VMsInUnknownPowerState
341 | return $result
342 |
--------------------------------------------------------------------------------
/XAXD/VMsInUnknownPowerState/LICENSE.MD:
--------------------------------------------------------------------------------
1 | Copyright 2023 Cloud Software Group, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2 |
3 |
--------------------------------------------------------------------------------
/XAXD/VMsInUnknownPowerState/README.MD:
--------------------------------------------------------------------------------
1 | # Get-ProvVmInUnknownPowerState
2 |
3 | This script is to diagnose and (semi-) automatically resolve VMs on Citrix Virtual Apps and Desktops (CVAD) in an unknown power state.
4 |
5 |
6 | ## Getting Started
7 |
8 | Official documentation of this script is available here: https://info.citrite.net/x/84xlXg
9 |
10 | (This link is an internal blog link. This will be updated after public blog is published.)
11 |
12 | ## Context and Problems
13 | The power state of the VMs on Citrix Virtual Apps and Desktops (CVAD) represents the power state of the corresponding VMs on hypervisors. However, in some cases, customers are experiencing VMs on CVAD continuously showing the power state unknown. Below are the reasons for the unknown power state.
14 |
15 | 1. The ID of the VM on CVAD mismatches with the corresponding ID of the VM on the hypervisor.
16 | 2. The VM on the hypervisor is deleted.
17 | 3. The hypervisor connection on CVAD is broken.
18 |
19 | The current practice to resolve the issue is helping the customers manually change the IDs of VM on CVAD stored on their database, change the IDs on the hypervisor, e.g., VMware, or delete and recreate the VMs. Those practices would be inconvenient and error-prone for customers, especially when a number of machines are in an unknown power state.
20 |
21 |
22 | ## A Solution: Get-ProvVmInUnknownPowerState
23 |
24 | Get-ProvVmInUnknownPowerState diagnoses the VMs on CVAD in an unknown power state, reports the causes, and (semi-) automatically fixes the causes. Specifically, Get-ProvVmInUnknownPowerState utilizes the names of VMs on CVAD to find the corresponding VMs on hypervisors and check three scenarios below that can make the power state of the VMs unknown.
25 |
26 | 1. Scenario 1: the state of the hypervisor connection is unavailable. such as connection credentials being invalid.
27 | 2. Scenario 2: the VMs on the hypervisor are deleted.
28 | 3. Scenario 3: the IDs of VMs on CVAD mismatches the corresponding IDs of the VM on hypervisors.
29 |
30 | Get-ProvVmInUnknownPowerState can take a hypervisor connection name, a broker catalog name, and/or "-Fix" as input.
31 |
32 | * If either a hypervisor connection name or a broker catalog is given, then Get-ProvVmInUnknownPowerState reports the VMs in an unknown power state associated with the hypervisor connection name or the broker catalog.
33 | * If both a hypervisor connection name and a broker catalog are given, then Get-ProvVmInUnknownPowerState reports the VMs associated with the broker catalog, because the VMs on the catalog is a subset of the VMs on the hypervisor connection
34 | * Without the "-Fix" parameter, the admin can get the report that includes the list of the VMs with the unknown power states together with the causes and suggestions.
35 | * With the parameter "-Fix", the admin can get the report, and the script automatically resolved the issue of scenario 3 by updating the IDs of VMs.
36 |
37 | In case of scenario 1 and 2, Get-ProvVmInUnknownPowerState reports the causes of the unknown power state and provide suggestions to help admins fix the issues, such as correcting the credentials or deleting the VMs on CVAD. In the case of scenario 3, Get-ProvVmInUnknownPowerState reports the causes and suggestions. If the "-Fix" parameter is specified, Get-ProvVmInUnknownPowerState fixes the causes of scenario 3 automatically.
38 |
39 | The scenario 1, 2, and 3 are exclusive. If the connection is unavailable, it is not possible to access the hypervisor; consequently, check whether the corresponding VM on the hypervisor is deleted or the IDs are matched.
40 |
41 | ## Prerequisites
42 |
43 | Minimal permission to execute Set-BrokerMachine is required.
44 |
45 |
46 | ## Built With
47 |
48 | * [Microsoft Powershell](https://msdn.microsoft.com/powershell)
49 |
50 |
51 | ## EXAMPLES
52 |
53 | The detailed examples are described in the blog.
54 |
55 |
56 | ## Versioning & Authors
57 |
58 | VERSION
59 | 1.0.1
60 |
61 | CONTRIBUTORS
62 |
63 | Haan Mo Johng, Engineering
64 |
65 | Charlie Wang, Engineering
66 |
--------------------------------------------------------------------------------
/config/allscripts.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name":"Director Reconfigure",
4 | "description":"Sets Director settings and also modifies the LogOn.aspx page to pre-populate the domain field and print the windows servername in the footer. Add as many of the web.config application settings as you want like page timeouts and domain search settings.",
5 | "link":["https://raw.githubusercontent.com/citrix/Powershell-Scripts/master/Director/DirectorReconfigureWithLogonMod.ps1"],
6 | "projectlink":"https://github.com/citrix/Powershell-Scripts",
7 | "author":"Johan Greefkes",
8 | "filename":"DirectorReconfigureWithLogonMod.ps1"
9 | },
10 | {
11 | "name":"Create Storefront Store (3.0)",
12 | "description":"Powershell script that creates a storefront store based on parameters.",
13 | "link":["https://raw.githubusercontent.com/citrix/Powershell-Scripts/master/Storefront/Create-Storefront3-Store.ps1"],
14 | "projectlink":"https://github.com/citrix/Powershell-Scripts",
15 | "author":"LASSE THESBJERG HOMANN",
16 | "filename":"Create-Storefront3-Store.ps1"
17 | }
18 |
19 |
20 | ]
21 |
--------------------------------------------------------------------------------