XenApplePie

Virtualization, technology, and random rantings with a focus on Citrix and VMware.

VDA Upgrade Script: Updated For 2402 LTSR

Another update for upgrading to 2402 LTSR from earlier versions!

The biggest change that I saw in this was the addition of the “/xenapp” switch in the Install_Server.bat and Install_Desktop.bat. For some reason, without that switch, it will not continue. According to the documentation at Install Switches, it says, “Command-line option: /xenapp to install Citrix Virtual Apps. Citrix Virtual Desktops is installed if this option is omitted.” What I found is that it wouldn’t install in either case correctly unless I added that switch. This will still do a WMI check and see if it is a server / desktop class OS and run the appropriate version of the files. This was tested against 2016, 2019, 2022, and multiple versions of Win 10.

$VDIList            = Get-Content "C:\scripts\logs\VDAUpgrade.txt"
$source             = "location.fqdn\Citrix\XenApp\vdaupgrade-2402"
$dest               = "c$\software\vdaupgrade"
$serverInstallFile  = "Install_Server.bat"
$serverRemoveFile   = "Remove_Server.bat"
$desktopInstallFile = "Install_Desktop.bat"
$desktopRemoveFile  = "Remove_Desktop.bat"
$VDAFileServer      = "VDAServerSetup_2402.exe"
$VDAFileDesktop     = "VDAWorkstationSetup_2402.exe"
$vcenter            = "vCenter Address"
$TotalItems         = $VDIList.Count
$CurrentItem        = 0
$PercentComplete    = 0
$date               = Get-Date -Format MMddyyyy
$report             = @()


if($global:defaultviserver -eq $null){

  Connect-VIServer $vcenter

}

if($GLOBAL:XDSDKProxy -eq $null){

  Get-XDAuthentication -ProfileName "default"

}


  foreach ($VDI in $VDIList) {
    Write-Progress -Activity "Starting on $VDI" -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
    $line                 = "" | Select-Object Name, PreviousVersion, SnapShot
      
    $VDI1                 = ($VDI.Split('.')[0])
    $line.Name            = "$VDI"
    $line.PreviousVersion = (Get-BrokerMachine -HostedMachineName $VDI1 | Select-Object AgentVersion).AgentVersion
    $snapshot             = (Get-VM $VDI1 | New-Snapshot -name $date-$VDI1-preupgrade)
    $line.SnapShot        = (Get-VM $VDI1 | Get-Snapshot).Name
    
    
    $wmiOSTypeCheck  = Get-WmiObject -ComputerName $VDI -Class Win32_OperatingSystem | Where-Object {$_.Primary -eq $true}
    
      if($wmiOSTypeCheck.ProductType -eq 3){
        $installFile = "$serverInstallFile"
        $removeFile  = "$serverRemoveFile"
        $VDAFile     = "$VDAFileServer"
        
     
        Write-Host "Copying VDA files and installing on server $VDI"
        if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
          New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
          Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
          
        }
        else {
          Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
        }
        
        Invoke-Command -ComputerName $VDI -Scriptblock {
          $time      = (Get-Date).AddMinutes(3)
          $action    = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\Remove_Server.bat'
          $trigger   = New-ScheduledTaskTrigger -Once -At $time
          $principal = New-ScheduledTaskPrincipal  -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U

          Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAUninstall" -Description "Citrix VDA Uninstall" 
        }

        Invoke-Command -ComputerName $VDI -Scriptblock {
          $action    = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\Install_Server.bat'
          $trigger   = New-ScheduledTaskTrigger -AtStartup 
          $principal = New-ScheduledTaskPrincipal  -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U

          Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAInstall" -Description "Citrix VDA Install" 

        } 
      
      }
      if($wmiOSTypeCheck.ProductType -eq 1){
        $installFile = "$desktopInstallFile"
        $removeFile  = "$desktopRemoveFile"
        $VDAFile     = "$VDAFileDesktop"

        Write-Host "Copying VDA files and installing on desktop $VDI"
        if (!(Test-Path -Path "\\$VDI\c$\software\vdaupgrade")) {
          New-Item -ItemType Directory -Path "\\$VDI\c$\software" -Name "vdaupgrade"
          Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
          
        }
        else {
          Copy-Item "\\$source\$installFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$removeFile" -Destination "\\$VDI\$dest" -Force
          Copy-Item "\\$source\$VDAFile" -Destination "\\$VDI\$dest" -Force
          
        }
        Invoke-Command -ComputerName $VDI -Scriptblock {
          $time      = (Get-Date).AddMinutes(3)
          $action    = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\Remove_Desktop.bat'
          $trigger   = New-ScheduledTaskTrigger -Once -At $time
          $principal = New-ScheduledTaskPrincipal  -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U

          Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAUninstall" -Description "Citrix VDA Uninstall" 
        }

        Invoke-Command -ComputerName $VDI -Scriptblock {
          $action    = New-ScheduledTaskAction -Execute 'c:\software\vdaupgrade\Install_Desktop.bat'
          $trigger   = New-ScheduledTaskTrigger -AtStartup 
          $principal = New-ScheduledTaskPrincipal  -RunLevel Highest -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U

          Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName "VDAInstall" -Description "Citrix VDA Install" 

        } 
      }
    
    $CurrentItem++
    $PercentComplete = [int](($CurrentItem / $TotalItems) * 100)
    
    $report += $line
    Start-Sleep -Milliseconds 2500
  }
      
  
$report 

VDAUpgrade.txt

machine1.fqdn
machine2.fqdn
machine3.fqdn

Install_Server.bat

C:\software\vdaupgrade\VDAServerSetup_2402.exe /noreboot /quiet /xenapp /components VDA /controllers "CloudConnector1.fqdn CloudConnector2.fqdn" /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in"



C:\Windows\system32\schtasks.exe /delete /tn VDAInstall /f
C:\Windows\system32\schtasks.exe /delete /tn VDAUninstall /f
del c:\software\vdaupgrade\remove.bat /F
del c:\software\vdaupgrade\VDAServerSetup_2402.exe /F
C:\Windows\System32\timeout.exe /t 5
C:\Windows\System32\shutdown.exe /r /t 20 /f
del c:\software\vdaupgrade\install.bat /F

Install_Desktop.bat

C:\software\vdaupgrade\VDAWorkstationSetup_2402.exe /noreboot /quiet /xenapp /components VDA /controllers "CloudConnector1.fqdn CloudConnector2.fqdn" /disableexperiencemetrics /enable_remote_assistance /enable_real_time_transport /enable_hdx_ports /enable_hdx_udp_ports /masterpvsimage /includeadditional "Citrix Personalization for App-V - VDA","Citrix Profile Management","Citrix Profile Management WMI Plug-in"

C:\Windows\system32\schtasks.exe /delete /tn VDAInstall /f
C:\Windows\system32\schtasks.exe /delete /tn VDAUninstall /f
del c:\software\vdaupgrade\remove.bat /F
del c:\software\vdaupgrade\VDAWorkstationSetup_2402.exe /F
C:\Windows\System32\timeout.exe /t 5
C:\Windows\System32\shutdown.exe /r /t 20 /f
del c:\software\vdaupgrade\install.bat /F

Remove_Server.bat

“C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe” /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Remove_Desktop.bat

"C:\Program Files\Citrix\XenDesktopVdaSetup\XenDesktopVdaSetup.exe" /REMOVEALL /QUIET /NOREBOOT
C:\Windows\System32\shutdown.exe /r /t 5 /f

Rolling Remove From Maintenance Script

I was seeing a longer time in upgrading from 2203CU2/CU3 to 2402 especially on VDI. For smaller vCPU amounts like 2 vs 4, the time was 15 minutes (4vCPU) versus nearly 30 minutes (2vCPU) to upgrade. To get machines back online faster, I modified another script I was using to identify what I needed to upgrade. The one issue you will have, is if you are running say 1912 and 2203 with different CUs, they would be below the targeted version in the script and not automatically removed from maintenance mode. You would have to manually remove those from maintenance mode.

Create a scheduled task, that has the appropriate rights and from a machine that can reach the devices, set it for a 5ish minute interval. In my case, I scheduled it from the same machine that was running the upgrade script. Another assumption is that you will need the PowerShell SDK for DaaS loaded on the machine as well. You will need to have a profile from that machine that has a connection profile named “default,” or change the ProfileName to match what yours happens to be.

You can get the $targetVersion information by upgrading one of your machines prior to the rest, which you should be doing and testing! Running the command Get-Brokermachine -HostedMachineName machinetobetested | Select-Object HostedMachineName, AgentVersion on the machine you updated will show the version.

asnp Citrix*

$report                        = @()
[System.Version]$targetVersion = "2402.0.100.629"
$getMachines                   = Get-BrokerMachine -MaxRecordCount 1000000

if($GLOBAL:XDSDKProxy -eq $null){

  Get-XDAuthentication -ProfileName "default"

}

foreach($machine in $getMachines){
  $line                   = "" | Select HostedMachineName, DNSName, AgentVersion, WillBeUpgraded, OperatingSystem, MaintenanceMode
  $testVersion            = $machine.AgentVersion
  
  $line.HostedMachineName = $machine.HostedMachineName
  $line.DNSName           = $machine.DNSName
  $line.AgentVersion      = $machine.AgentVersion
  $line.OperatingSystem   = $machine.OSType
  $line.MaintenanceMode   = $machine.InMaintenanceMode
  
  if([System.Version]$testVersion -ge [System.Version]($targetVersion)){

  $line.WillBeUpgraded    = "Current Version Or Newer"
  
  }
  
  if([System.Version]$testVersion -lt [System.Version]($targetVersion)){

  $line.WillBeUpgraded    = "Yes"
  
  }

  
  $report += $line


}


$removeFromMaintenanceMode = ($report | Where-Object {($_.WillBeUpgraded -notlike "Yes" -and $_.MaintenanceMode -eq "True")} | Select-Object HostedMachineName).HostedMachineName


foreach($remove in $removeFromMaintenanceMode){


Get-BrokerMachine -HostedMachineName "$remove" | Set-BrokerMachine -InMaintenanceMode $false


}

Getting Reliability Monitor Info Remotely

Sometimes you just want to pull a quick check of Reliability Monitor remotely. Easy to do!

Get-WmiObject Win32_ReliabilityRecords -ComputerName machineIP -Property Message | Select-Object -first 10 Message | Format-List *

Citrix Mac VDA Setup

Excellent guide below on setting up the Citrix Mac VDA.

https://community.citrix.com/tech-zone/build/deployment-guides/citrix-mac-vda/

One little caveat I ran into with this. Once you add additional machines to the Machine Catalog and are ready to add to the Delivery Group, you will see this message when you want to add machines:

I found that it did not automatically add the machines to the Delivery Group. I had to go to the Machine Catalog and select “View Machines.” After doing this, you have to right-click the newly added machine and select “Add to Delivery Group.” Once you complete that step and “Change User,” it will show up in the Delivery Group.

In this example, you see the option to remove. This option is after you have added it to the Delivery Group.

In-depth Review: Goliath Performance Monitoring

I have had some time to really check out this software and put it through its paces and bring my findings. Goliath Performance Monitoring software. I have been really surprised with what I was able to accomplish with it. But the proof is in the pudding, so pudding I shall prepare!

First with it. Pretty simple installation. You just need a server to run the software and a database / database server (SQL) to host the information. Some exclusions for AV / security software are rather important as well. Setting up the inventory and such was a breeze as the personnel at Goliath were willing to assist setting it all up. I explained the environment I wanted to test with and the resources I wanted to monitor and they walked through the paces with me. Then I had a working monitoring solution.

The question you have when you have a monitor solution, is how to make it monitor what you want without TONS of alert fatigue. It is easy to get overwhelmed by alerts that may mean nothing and take valuable time away. The default monitor rules it came with already configured were mostly sufficient and I didn’t notice a bunch of unwanted alerts. There were even some alerts I wasn’t expecting to see. One was an instance where Citrix Cloud went offline for a short time. I got an alert saying DaaS down and LHC engaged. I went and checked status.cloud.com to see what was going on. I didn’t see anything for a few minutes and then all of sudden, it displayed there was an issue going on. I was alerted quite a bit before even the status console showed it. That was rather handy to know.

Setting up a custom alert outside of the defaults was easy enough as well. I configured one to monitor FAS in case of issue where it didn’t like to issue certs. Simple setup and added the remediation (which you can configure a myriad of options such as run this script or reboot this server). This allows you to not only alert on the issue, but to do something about it if there is a known fix. This has been a real help with that.

But wait, there’s more! So I’ve just been talking about how to use the basic monitoring alerts. Well, there are also several views that are available for user sessions. One thing I found myself using on the regular, is the “Published Apps and Desktop,” and the “Virtual Desktops” tab. Here is a bunch of user information that can help solve some issues. There is a column you can add of “Connection speed” that quickly has helped identify end user issues at home ISPs. You can also see machine health status and session information over time which is useful to be able to track patterns of issues.

The views contain a lot of useful information on the high level such as ICA RTT and ICA latency. That quick glance can show if there is an issue with user connections or other issues with getting responses back. You can also see the version of the client they are using as well as the method such as client or HTML5 client. You can also modify the view to show a specific user or machine. You can also select a custom time period to see trending information. You can select a session and drill into it for more information. Starting off with the Published Applications and Desktops tab, right off the bat you see a lot of data. You get machine performance and session metrics. The top 5 processes is very useful to see any runaway program or possible scanning issue with things such as A/V.

There are also tabs to select different areas of the session. The Logon tab drills into the GPO processing, which shows which policies were applied and how long it took to process them.

The ICA/HDX tab breaks down things such as ICA performance and connectivity metrics from the client machine. You very quickly can see the available and used bandwidth. This could assist in seeing if their connection is saturated.

The App Server tab shows the metrics on the app hosting server, revealing any bottlenecks in IO, processor, or RAM saturation. An additional tab is there for the Hypervisor Host. This lays out the same metrics but for the underlying host hardware. Getting this tiered information helps you see the whole stack interacting and points out issues with it very efficiently.

You also get the same kind of views related to Virtual Desktops. You see each machine in use and can select the session there as well.

Another aspect of the monitoring is the EUC Scorecard that they helped setup as a daily and a weekly report. This contains a lot of information of the top session issues, connectivity issues, and user experience. Reviewing this on the daily can show if you have some locations that may need upgrades in connectivity or if there is something else going on. This helps you be more proactive in solving an issue. Another good use for trending is reviewing the weekly report and comparing to last week’s report. For example, if you see the same users across weeks, this could point to an issue with a site or possibly a need to upgrade bandwidth at a site. Users don’t always call when something is going on. This lets you get in front of it and users appreciate when they are put first and you contact them and let them know you see there is an issue and that you are going to try and solve it for them BEFORE they call you.

Then comes along new features across upgrades. One that was rather nice, was the addition of Chrome OS device monitoring. You can integrate with a Google tenant and monitor Chrome devices. This is fairly easy to configure and they will walk with you to get it done quickly. You see immediately once you add that, all the RAM / CPU use and network health of the device. Being able to see that could very much help with knowing are you overloading the devices and may need larger resource devices. You could also see if there are connectivity issues with them if they are dropping connection and such.

Then a really neat feature came to the software, Ask Kip! AI integration with the monitoring software. I thought how this would assist in my testing. Well, often you get into a set and forget mindset unless there is something off that requires you to add new monitor rules or changing something with alerts. Very rarely did I need to add any new ones, but I did get an alert that was on repeat as it should have been. I went to the console and entered my question on alert suppression into Ask Kip! and it laid out the steps as to what I needed to do in order to suppress the alert. Was straightforward and it had each step of the rule and setting the alert parameters.

I decided I would see what all it could assist with in relation to the software. I asked it how would I remedy a slow user connection (I know the steps, I just wanted to see what it told me). It walked through the same steps that I would have done to solve it. I asked how to add hardware inventory to manage as well as hide inventory I didn’t want to see. Step by step instructions right there to do it.

Another good feature that is available as part of the suite, is the Application Availability. This is particularly useful if you have multiple sites and want to check availability on a regular basis. You can set it up on a machine at each of your sites and have it launch whatever apps are necessary or mission critical to monitor to assure that uptime. It launches the applications you designate on the schedule you define and reports if there are any issues as well as successful launches. This would be invaluable data if you are wanting to assure that all of your remote sites are able to access and to spot down times as soon as they occur to be able to mitigate as fast as possible. It breaks down the instances to Access, Authentication, Resources, Enumeration, and Launch. By having that quick breakdown, you see where the issue is quickly. You will be able at a glance to see where the communication break down occurs and know where to start looking for resolution. That will save valuable time not wasted on checking things that are working correctly and allow you to focus on the specific area that is broken.

Citrix topology is a really neat feature as well. It takes information from the configuration and lays out a visual mapping to quickly understand dependencies and see them on a diagram. You can do this with multiple sites as well. Alerts are shown on the mapping as well as color coding to show quickly if there are issues.

Another feature that has been added, is Cloud Monitoring. This is a handy feature if you are setup in AWS or Azure and want to be able to view your environments there. For hybrid on-prem / cloud based solutions, this is a wonderful addition. Many customers today are moving into hybrid models and Goliath is keeping up with that trend. This being in the same Goliath console, allows for close to a single pane of glass view into your EUC environments.

Was everything perfect, no. No software exists that doesn’t need some tweaking or code fixes or a setting change to get it back on track. I ran into a couple of issues with the software. I contacted their support and got immediate responses. They issued more than one code fix to address issues that were encountered. They were personable and friendly and assistive even with email questions I would have about the software. They are regularly adding features and working to make it an even better solution.

This is a solution that provides Citrix admins a great tool set to make the job easier and get faster times to resolution! This would be a product I would recommend!

When You Add A Cloud Connector, Don’t Forget The Cert Bind!

When you need to add another cloud connector, it’s really easy to forget a step or two since you don’t necessarily do it all the time. But don’t forget to add it to the STA list on the your Citrix Gateway on Netcaler and as a Delivery Controller on your Storefront servers. But…. Rather important if you are using HTTPS (which you should be), to make sure and have a cert on the Cloud Connector (server /client) and bind it using the script below. Since IIS isn’t installed on the Cloud Connectors, this is how you can do it! Article below has screenshots of getting cert and App ID. This is for after you already have gotten App ID and the certificate installed. Also, certificates expire, so you will have to get new certs when they expire and bind them the same way. I added the $beforeCertificateBind and $afterCertificateBind so you have an output of what was there before and after. You can get the App ID by following the link: Enable SSL

$beforeCertificateBind = (& C:\Windows\System32\netsh.exe http Show sslcert)
$beforeCertificateBind

# Confirm AppID.
$appID   = "12345678-1234-1234-1234-1234567890AB"
$getHash = (Get-ChildItem -path cert:\LocalMachine\My | Where-Object Subject -Match "$env:COMPUTERNAME" | Select-Object Thumbprint).Thumbprint

& C:\Windows\System32\netsh.exe http add sslcert ipport=0.0.0.0:443 certhash=$getHash appid=`{$appID`}

$afterCertificateBind = (& C:\Windows\System32\netsh.exe http Show sslcert)
$afterCertificateBind

Copy Training Video And Publish As App

Sometimes you need users to see training videos. You may have to copy it to several servers. You may need to copy several videos to several servers. There might be an instance where it is a new implementation or you have had videos there before. This will copy videos to a group of servers in a Delivery Group and publish the app to the Application Group of your choosing.

$remoteMachines        = (Get-BrokerMachine -MaxRecordCount 100000 | Where-Object DesktopGroupName -Match "DeliveryGroupName" | Select-Object DNSName).DNSName
$sourcePath            = "PathToVideo.fqdn"
$videosToCopy          = Get-ChildItem -Path $sourcePath | Select-Object Name
$destinationPath       = "c$\training-video-folder"
$applicationType       = "HostedOnDesktop"
$commandLineArguments  = "c:\training-video-folder"
$commandLineExecutable = "%ProgramFiles(x86)%\Windows Media Player\wmplayer.exe"
$workingDirectory      = "%ProgramFiles(x86)%\Windows Media Player"
$applicationGroup      = "App Group Name"
$iconUid               = "21"

$totalItems            = $remoteMachines.Count
$currentItem           = 0
$percentComplete       = 0

foreach($remote in $remoteMachines){
  
  Write-Host "Checking if folder " $destinationPath " exists on " $remote
  if (!(Test-Path -Path "\\$remote\$destinationPath")) {
        Write-Host "Creating folder " ($destinationPath).Split('\')[1] " on $remote"      
        New-Item -ItemType Directory -Path "\\$remote\c$" -Name ($destinationPath).Split('\')[1]
  }

  foreach($video in $videosToCopy){
    Write-Progress -Activity "Copying $video to $remote" -Status "$percentComplete% Complete:" -PercentComplete $percentComplete
    Copy-Item "\\$sourcePath\$video" -Destination "\\$remote\$destinationPath\" -Force
    
    $currentItem++
    $percentComplete = [int](($currentItem / $totalItems) * 100)
    
  }
}

foreach($vid in $videosToCopy){

$videoName = ($vid.Name)
$shortName = ($vid.Name).Split('.')[0]

New-BrokerApplication -ApplicationType "$applicationType" -Name "$shortName" -BrowserName "$shortName" -CommandLineExecutable "$commandLineExecutable" -CommandLineArguments "$commandLineArguments\$videoName" -Description "$shortName" -WorkingDirectory "$workingDirectory" -ApplicationGroup "$applicationGroup" -IconUid "$iconUid"

}

Quick Function To Find User VDI

Sometimes you need to find a user’s VDI machine to work on it. This function will do that for you. I typically use the last name as the search to limit the scope of the machines found. It will find all machines that contain any part of the string you enter. It also shows the MachineName which includes the domain\machinename to help locate the user machine. You can add other parameters such as AgentVersion if desired. I limited the scope to not include floating pool (Random) assigned machines. For a list of all fields of Get-BrokerMachine that can be selected in the function with Select-Object, please see this link: Get-BrokerMachine Options

# Requires being connected to Citrix Cloud with DaaS SDK. 
Function Get-VDI {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$VDIUserName
    )
   
    Process {
       $userToFind = '*' + $VDIUserName + '*'
       $getVDIs = Get-BrokerMachine -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object AllocationType -eq "Static" | Where-Object AssociatedUserNames -like "$userToFind" | Select-Object AssociatedUserNames, MachineName, RegistrationState, InMaintenanceMode, SessionCount
    }
    End {
        $getVDIs
    }
}
Example with user that has 4 desktops assigned.

Also to make it is easy for the day to day, you can add this to your profile with notepad $profile and copy and paste it there and reload.

Get User Info From Email Address

If you sometimes have to find a user list and get group memberships, it can be a chore. It doesn’t have to be. You too can experience a scripted approach to getting that data. This will search the AD Forest’s sub domains that you have rights to at least read to get user memberships that follow a specific pattern. Very useful when you have one domain for groups and users from many domains. Universal groups are great for that. You just need to populate the referenced “useremail.txt” file with user email addresses and deleting any white space in the file. You can also get additional information in the report by adding in the “line = “” | Select-Object” and adding additional fields and then matching the name designated with the “line.Fields = syntax.”

# Script to search domains in forest for users via user email address. You could also search via UPN by replacing EmailAddress with UserPrincipalName.
# This requires AD module, read rights to forest / domains, and user group pattern. Ran with ISE and tested on desktop / server platforms. This also uses
# the global catalog lookup as seen referenced by Get-ADUser -server domain:3268. Assistance from Notesofascripter.com in making this.
$debug      = $true
$domains    = (Get-ADForest).Domains
$date       = Get-Date -Format MMddyy
$userExists = ""
$reportName = "nameofreport.csv"
$report     = @()

$emailList  = Get-Content -Path "c:\scripts\logs\useremail.txt"

$emailSam   =  foreach($user in $emailList) {
    (Get-ADUser -server domain:3268 -f {EmailAddress -eq $user}).SamAccountName
  }

$users      = $emailSam

foreach ($domain in $domains){
    if ($debug){Write-Host $domain -ForegroundColor Cyan}
    foreach ($user in $users) {
        Try{
            $userExists = Get-ADUser $user -Server $domain -ErrorAction Stop
        }
        Catch {
            if ($debug){Write-Host "$user not found in $domain" -BackgroundColor yellow -ForegroundColor black}
            $userExists = $null
            Continue
        }
        if ($userExists -ne $null){
            if ($debug){ Write-Host "Found $user in $domain domain" -BackgroundColor Cyan -ForegroundColor Black}
            $group = $userExists | Get-ADPrincipalGroupMembership | Where-Object Name -like "GroupNamePattern*" -ErrorAction Stop
            $line          = "" | Select-Object Name, Domain, RealName, Group
            $line.Name     = $user
            $line.Domain   = $domain
            $line.RealName = $userExists.name
            $line.Group    = $group.name -join "; "
            if ($debug){ $line }
            $report       += $line
            $users         = $users -ne $user
            
        }
    }
}

$report | Export-Csv "c:\scripts\logs\$date-$reportName" -Append -NoTypeInformation

When Trouble Comes Along, You Must Skip It! : Using -Skip With Select-Object And Do Loop

While working on the script to release licenses, I ran into a dilemma I had encountered before. I needed to move through a set of records in an array, but not the whole array at one time. Since I was limited to 100 licenses released per Invoke-RestMethod, I needed a way to do that without having to call it multiple times. I had also encountered this at other times and usually resorted to just doing the call multiple times and limiting my results to the number I wanted to iterate through. I didn’t think that was efficient and wanted to find a better way. So in looking around, I found this site: ResultSize. Using that wonderful -Skip, I just added that to a Do .. Until loop to get where I wanted to go.

So applying what I wanted to do was 100 records at a time, I set my $toSkip counter to 0 to start and the $smallerCount set to the value of the $someArray.count size inside the If check just before the Do loop. After each loop, I would increment the $toSkip and decrement the $smallerCounter by the same amount and set the Until condition to be less that the increment / decrement amount. Then I copied the same code for after the Until condition is met to run one more time with the amount of objects remaining in the array. For example, if it had 435 items in it, it would run 4 times with 100 items each time and one last time with 35 items in it.

$someArray = "lots", "of", "stuff", "here"

if($someArray.count -gt 100){

$smallerCounter = $someArray.Count
$toSkip = 0

Do{

$smallerList = $someArray | Select-Object -Skip $toSkip -first 100

foreach($small in $smallerList){

Do Stuff

}

$toSkip += 100
$smallerCounter -= 100

} Until ($smallerCounter -lt 100)

$smallerList = $someArray | Select-Object -Skip $toSkip -first $smallerCounter

foreach($small in $smallerList){

Do Stuff

}

}

Page 1 of 12

Powered by WordPress & Theme by Anders Norén