├── 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 | --------------------------------------------------------------------------------