Tag Archives: XenDesktop

WEM 4.3 Upgrade Available

63426576

Intro

As is often the case, Citrix incremented almost all the versions of their products during the Citrix Synergy conference. Included with the new release of XenApp/XenDesktop 7.14 was WEM version 4.3. You can now download the new version here (requires Platinum licenses and login to Citrix.com). I’ve provided the release notes below.

What’s new

Site management

In previous releases, site settings were stored on the agent side and it was possible to change them from the agent GPO. Workspace Environment Management 4.3 introduces a different approach to site management which improves product security. Sites are now assigned to machines (or Security Groups or OUs) by the infrastructure service (broker) using a new Machines page in the administration console. A new Registrations tab under Administration>Agents in the administration console indicates machines which are bound incorrectly to multiple sites, so that you can take the appropriate action to remove the duplicate binding. A new Registrations tab on the Agents page shows agent registration information.

From this release, Workspace Environment Management “sites” are referred to as “configuration sets” in the user interface and documentation.

Agent localization improvements

The session agent user interface is now localized for the following languages: German, Spanish, French, Italian, Japanese, Korean, Dutch, Russian, Traditional and Simplified Chinese.

User interface improvements

Various text labels and messages in the installation wizards, administration console, and GPO templates have been rationalised and made mutually consistent to improve the user experience. For example, fields used to enter the same parameters in different installation wizards now use the same labels. Current and changed terminology is describe in a new glossary.

Documentation

Workspace Environment Management 4.3 documentation is updated to reflect current product behaviour. Various minor improvements have also been made, including the following improvements designed to assist users:

  • a number of installation field descriptions have been revised to better explain their purpose
  • the documentation uses new standardized terminology visible in the installation wizards, GPO templates, and in the administration console. For example, the term “broker” is replaced by “infrastructure service”.
  • a glossary has been added to explain the new terminology seen in the installation wizards, the administration console, and the documentation. Changed terms are also indicated.
  • the technical overview diagram is updated
  • a new port information table has been added to summarize port usage

Fixed issues

The following issues have been fixed since Version 4.3:

  • When the Workspace Environment Management session agent is running in command line mode, User Statistics data is not reported to the WEM infrastructure services. [WEM-41]
  • The Workspace Environment Management session agent interface does not render correctly when a computer display is extended to external displays connected via a dock. This problem, which occurs when extending to multiple displays with different screen resolution settings, results in a portion of the right-hand side of the display not rendering completely. This prevents users seeing the home button or being able to change other native Workspace Environment Management settings. [WEM-90]
  • The Workspace Environment Management session agent causes the mouse to stop working on virtual machines which have the System Center Configuration Manager (SCCM) client installed with Power Management enabled. [WEM-115]
  • When you are using the Transformer feature, the session agent generates an unhandled exception if Wi-Fi is turned off using “ms-settings:network-wifi.” [WEM-133]
  • The Workspace Environment Management session agent causes the mouse to stop working on virtual machines after an interruption to network access is restored. [WEM-159]

Known issues

This release contains the following issues:

  • On Windows Server 2012 R2, if Adobe Acrobat Reader is installed it prevents Workspace Environment Management associating files of type .PDF with other PDF reader applications. Users are forced to manually select the PDF reader application to use each time they open a PDF. [#WEM-33]

Deprecated

Item Announced in Alternative

Support for assigning and binding existing (pre-version 4.3) agents to sites via GPO.

4.3

Upgrade agents to Workspace Environment Management 4.3.

The Administration Console will not be supported on the following platforms after the next LTSR:

Windows XP SP3 32-bit and 64-bit
Windows Vista SP1 32-bit and 64-bit
Windows 8.x 32-bit and 64-bit
Windows Server 2003 32-bit and 64-bit
Windows Server 2003 R2 32-bit and 64-bit Windows Server 2008
Windows Server 2008 R2

4.2

Workspace Environment Management will not be supported on the following software after the next LTSR:

Microsoft .NET Framework 4.0
Microsoft .NET Framework 4.5.0
Microsoft .NET Framework 4.5.1

4.2

Removed

The following platforms, Citrix products, and features are either removed in Workspace Environment Management 4.3 or are no longer supported in Workspace Environment Management 4.3.

Item Replacement

Support for assigning and binding version 4.3 agents to sites via GPO.

Assign and bind version 4.3 agents to sites via administration console.

Thanks for reading,
Alain

Powershell: The Ultimate User Deletion Script?

akkyu

Intro

I’ve spent the better part of 4 days working on a monster script. I needed this script to perform the following actions:

  1. Take a list of usernames and ensure they exist in Active Directory (AD).
  2. Take a list of usernames and disable their AD accounts
  3. Take a list of usernames and delete their AD accounts
  4. Take a list of usernames and delete any files in their User profile and RDS profile
  5. Take a list of usernames and delete any local profiles present on XenApp Servers
  6. Take a list of usernames and delete any of their assigned XenDesktops
  7. Take a list of usernames and remove their account from any published application
  8. Review this list of usernames and remove any accounts that should not be removed
  9. Provide feedback with write-verbose messages and create a log file of any actions

Needless to say, this script can easily be broken down into different functions for reusability, but I wanted an all-in-one script that would be used by other support team members. This script assumes PowerShell 2.0 and that the following cmdlets are available:

  • Microsoft Active Directory
  • Citrix XenApp Commands (SDK for XenApp 6 or 6.5 so you can run remote commands)
  • Citrix XenDesktop Commands

The user who runs the script should be a AD domain , XenApp and XenDesktop admin.  I do not recommend using this script as is, but you may find parts you can use in your environment.  Also review the URL’s in the .LINK section. I used ideas and code from these web pages to help write this script.

You can edit the #CONSTANTS section for your environment.

The Script

<#
.SYNOPSIS
	Takes ad username or object of usernames and deletes user's resources and account from the domain and Citrix environment.
.DESCRIPTION
	Removes a user's account and resources from the AD domain and Citrix environment.

	It is recommended that this script be run as an admin. In addition, the Microsoft Active Directory, XenDesktop, XenApp Powershell Cmdlets must be available for user and desktop deletion.
.PARAMETER username
      Required parameter.
      User account(s) that will be deleted. User accounts must be disabled before deletion. See disable parameter.
.PARAMETER disable
    Optional switch parameter.
    Defaults to $false.
    If present, user accounts will just be disabled.
.EXAMPLE
	PS C:\PSScript > .\delete-citrixuser.ps1 -username "someuser"

	Will use all default values.
    No feedback messages will be shown.
    All user accounts are expected to be already disabled otherwise, no accounts will be deleted.
.EXAMPLE
	PS C:\PSScript > .\delete-citrixuser.ps1 -username "someuser" -verbose

	Will use all default values.
    Feedback/progress messages will be shown.
    All user accounts are expected to be disabled otherwise, no accounts will be deleted.
.EXAMPLE
    PS C:\PSScript > .\delete-citrixuser.ps1 -username "someuser" -disable -verbose

    Will use all default values.
	Will set AD user accounts to disabled.
    Feedback/progress messages will be shown.
.INPUTS
	Username or object of usernames.
.OUTPUTS
	To see feedback messages use the -verbose common parameter. No objects are output from this script.  This script creates a user deletion log.
.NOTES
	NAME: delete-citrixuser.ps1
	VERSION: 1.00
    CHANGE LOG - Version - When - What - Who
                 1.00 - 07/21/2014 - Initial script - Alain Assaf
	AUTHOR: Alain Assaf
	LASTEDIT: July 21, 2014
.LINK
    http://www.linkedin.com/in/alainassaf/
    http://wagthereal.com
    http://stackoverflow.com/questions/11605893/checking-for-the-existence-of-an-ad-object-how-do-i-avoid-an-ugly-error-message
    http://powershell.com/cs/blogs/tips/archive/2009/06/26/using-switch-parameters.aspx
    http://technet.microsoft.com/en-us/library/ee692802.aspx
    http://explorepowershell.com/2012/12/24/checking-setting-remote-desktop-services-profile-settings/
    http://winpowershell.blogspot.com/2006/08/suppressing-output-using-out-null-and.html
    http://blogs.msdn.com/b/powershell/archive/2009/12/29/arguments-for-remote-commands.aspx
    http://techibee.com/powershell/powershell-script-to-delete-windows-user-profiles-on-windows-7windows-2008-r2/1556
    http://stackoverflow.com/questions/12727388/wildcard-with-variable-in-get-aduser
    http://ss64.com/ps/do.html
    http://technet.microsoft.com/en-us/library/ee177002.aspx
    http://technet.microsoft.com/en-us/library/ee176955.aspx
    http://support.citrix.com/static/kc/CTX127254/help/
    http://social.technet.microsoft.com/Forums/windowsserver/en-US/79f9be3c-2945-471d-8a60-a1390f376d6e/removeaduser-has-no-force-option?forum=winserverpowershell
    http://titlerequired.com/2012/10/29/powershell-make-it-do-something-useful/
#>

Param(
    [parameter(Position = 0, Mandatory=$True )]
    [ValidateNotNullOrEmpty()]
	$username,

    [parameter(Position = 1, Mandatory=$False )]
    [ValidateNotNullOrEmpty()]
    [switch]$disable
	)

### FUNCTION: get-mymodule #####################################################
Function Get-MyModule {
    Param([string]$name)
    if(-not(Get-Module -name $name)) {
        if(Get-Module -ListAvailable | Where-Object { $_.name -eq $name }) {
            Import-Module -Name $name
            $true
        } #end if module available then import
        else { $false } #module not available
        } # end if not module
    else { $true } #module already loaded
}
### FUNCTION: get-mymodule #####################################################

### FUNCTION: get-mysnapin #####################################################
Function Get-MySnapin {
    Param([string]$name)
    if(-not(Get-PSSnapin -name $name)) {
        if(Get-PSSnapin -Registered | Where-Object { $_.name -eq $name }) {
            add-PSSnapin -Name $name
            $true
        } #end if module available then import
        else { $false } #snapin not available
        } # end if not snapin
    else { $true } #snapin already loaded
}
### FUNCTION: get-mysnapin #####################################################

### FUNCTION: Check-ADUser #####################################################
#Function to check if user account exists in AD
Function Check-ADUser
{
    Param ($usrname)

    $isuser = $(try {Get-ADUser $usrname} catch {$null})
    if ($isuser -ne $null) {
        return $true
    } else {
        return $false
    }
}
### FUNCTION: Check-ADUser #####################################################

### FUNCTION: Remove-UserProfile################################################
Function Remove-UserProfile
{
    param(
        [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:computername,            

        [parameter(mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$UserName, 

        [parameter(mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$localpath 

    )            

    foreach($Computer in $ComputerName) {
        Write-Verbose "Looking for local profiles on $Computer"
        if(Test-Connection -ComputerName $Computer -Count 1 -ea 0) {
            $UserProfile = Get-WmiObject Win32_UserProfile -Computer $Computer -ea 0 -filter “localpath='$localpath'”
            if (!$UserProfile) {
                write-Verbose “$Username not found on $Computer”
            } else {
                $UserProfile | Remove-WmiObject
                Write-Verbose "$UserName profile deleted successfully on $Computer"
            }
        }  else {
            write-verbose "Cannot connect to $Computer"
        }
    }
}
### FUNCTION: Remove-UserProfile################################################

#Constants
$datetime = get-date -format "MM-dd-yyyy_HH-mm"
$Domain=(Get-WmiObject Win32_ComputerSystem).Domain
$UserOU="OU=Users,DC=domain,DC=local"
$CloudProfilePath = "\\userprofileserver.domain.local\upm\"
$CloudRDSProfilePath = "\\userprofileserver.domain.local\profiles\"
$ScriptRunner = (get-aduser $env:username | select name).name
$XAZDC = "XenAppZDC.domain.local"
$PSModules = ("activedirectory")
$PSSnapins = (
    "Citrix.ADIdentity.Admin.V1",
    "Citrix.Broker.Admin.V1",
    "Citrix.Common.Commands",
    "Citrix.Common.GroupPolicy",
    "Citrix.Configuration.Admin.V1",
    "Citrix.Host.Admin.V1",
    "Citrix.LicensingConfig.Admin.V1",
    "Citrix.MachineCreation.Admin.V1",
    "Citrix.MachineIdentity.Admin.V1",
    "Citrix.XenApp.Commands")
$accountnames = New-Object System.Collections.ArrayList
#Any account in DONOTDELETE array will always be removed from accountnames array and will not be disabled or deleted.
$DONOTDELETE = New-Object System.Collections.ArrayList
$DONOTDELETE.Add("Guest")>$null
$DONOTDELETE.Add("krbtgt")>$null

#Get List of XenApp Servers
$XAServers = get-xaserver -ComputerName $XAZDC | select servername

#Populate accountnames
foreach ($a in $username) {$accountnames.Add($a)>$null}

#Remove any users in accountnames who are in the DONOTDELETE array
foreach ($b in $DONOTDELETE) {$accountnames.remove($b)>$null}

#initalize output array
$finalout = @()

#Import Module(s) and Snapin(s)
foreach ($module in $PSModules) {
    if (!(get-mymodule $module)) {
        write-verbose "$module PowerShell Cmdlet n ot available."
        write-verbose "Please run this script from a system with the $module PowerShell Cmdlets installed."
        exit
    }
}
foreach ($snapin in $PSSnapins) {
    if (!(get-MySnapin $snapin)) {
        write-verbose "$snapin PowerShell Cmdlet not available."
        write-verbose "Please run this script from a system with the $snapin PowerShell Cmdlets installed."
        exit
    }
}

#Confirm OU is valid
If (!([adsi]::Exists("LDAP://$UserOU"))) {
    write-verbose "$UserOU IS NOT VALID"
    write-verbose "Please use the following format: OU=Users,DC=domain,DC=local"
    Exit
}

#Confirm user(s) exists
foreach  ($usr in $accountnames) {
    $UserStatus = Check-ADUser $usr
    If ($UserStatus) { #User Exists - delete or disable user
        write-verbose "USER EXISTS: $usr"
        If ($disable) { #Disable is true - just disable the user
            write-verbose "DISABLING: $usr"
            disable-ADAccount -Identity $usr
            get-aduser $usr -properties info | foreach { Set-ADUser -Identity $_.samaccountname -Replace @{info="$($_.info)`r`nAccount disabled on: $datetime by $ScriptRunner"} }
            $finalout += "$usr disabled by $ScriptRunner on $datetime"
        } Else {        #Disable flag is false - delete user (if already disabled)
            #Confirm user account is disabled
            $citrixuser = Get-ADUser $usr -properties profilepath | select -ExcludeProperty disting*
            if ($citrixuser.Enabled) { #User account is not disabled - write error and go to next user
                write-verbose "$usr is not disabled. Please disable prior to deletion."
                $finalout += "$usr is not disabled. Please disable prior to deletion."
            } else { #user account is disabled clean up and delete user account.
                #Get User's profile and RDS profile path
                $userprofilepath = $citrixuser.profilepath
                $citrixuser = [ADSI]“LDAP://$citrixuser”
                $rdsprofilepath = $citrixuser.psbase.InvokeGet(“terminalservicesprofilepath”)
                #Delete user's profile and RDS profile
                if (test-path $userprofilepath) {
                    remove-item -Recurse -Force $userprofilepath
                    write-verbose "FOLDER DELETED: $userprofilepath"
                    $finalout += "FOLDER DELETED: $userprofilepath"
                } elseif (test-path ($userprofilepath + ".V2")) {
                    remove-item -Recurse -Force ($userprofilepath + ".V2")
                    write-verbose ("FOLDER DELETED: $userprofilepath" + ".V2")
                    $finalout += ("FOLDER DELETED: $userprofilepath" + ".V2")
                } else {
                    write-verbose ("FOLDER DOES NOT EXIST: $userprofilepath or $userprofilepath" + ".V2")
                }
                if (test-path $rdsprofilepath) {
                    remove-item -Recurse -Force $rdsprofilepath
                    write-verbose "FOLDER DELETED: $rdsprofilepath"
                    $finalout += "FOLDER DELETED: $rdsprofilepath"
                } elseif (test-path ($rdsprofilepath + ".V2")) {
                    remove-item -Recurse -Force ($rdsprofilepath + ".V2")
                    write-verbose ("FOLDER DELETED: $rdsprofilepath" + ".V2")
                    $finalout += ("FOLDER DELETED: $rdsprofilepath" + ".V2")
                } else {
                    write-verbose ("FOLDER DOES NOT EXIST: $rdsprofilepath or $rdsprofilepath" + ".V2")
                }
                #Delete user's local profile on XenApp servers
                foreach ($srv in $XAServers) {
                    $tmpdir = invoke-command -computername $srv.servername {"$env:homedrive\users"}
                    $usrdir = $tmpdir + "\" + $usr
                    remove-userprofile -computername $srv.servername -username $usr -localpath $usrdir.Replace("\","\\") -verbose
                }
                #Delete any assigned XenDesktops
                $user = $Domain.split(".")[0] + "\" + $usr
                $UserDesktops = Get-BrokerDesktop -AssociatedUserName $user -DesktopKind "Private"
                if ($UserDesktops -ne $null) {
                    foreach ($d in $UserDesktops) {
                        set-brokerprivatedesktop $d.machinename -InMaintenanceMode $true
                        do {
                            start-sleep -s 5
                            $result1 = Get-BrokerPrivateDesktop -MachineName $d.Machinename
                        } until ($result1.InMaintenanceMode -eq $true)
                        write-verbose "$d.machinename - maintenance mode enabled"
                        New-BrokerHostingPowerAction -Action 'Shutdown' -MachineName $d.machinename
                        do {
                            start-sleep -s 5
                            $result2 = Get-BrokerHostingPowerAction -MachineName $d.MachineName | select -last 1
                        } until ($result2.State -eq "Completed")
                        write-verbose "$d.machinename - powered down"
                    }
                    #Unassign user from desktop
                    Remove-BrokerUser -Name $user -Machine $d.machinename
                    #Remove desktop from DesktopGroup
                    remove-Brokermachine -Machine $d.machinename -desktopgroup $d.desktopgroupuid
                    #Delete desktop and AD Computer Account
                    Remove-BrokerMachine -MachineName $d.machinename
                    Remove-AcctADAccount -IdentityPoolName $d.Catalogname -ADAccountName $d.machinename -RemovalOption 'Delete'
                    write-verbose "$d.machinename deleted from Desktop Studio and Active Directory"
                    $finalout += "$d.machinename deleted from Desktop Studio and Active Directory"
                } else {
                    write-verbose "No desktops associated with $user"
                    $finalout += "No desktops associated with $user"
                }
                #Remove user from any Citrix published resources
                $apps = (Get-XAApplicationReport -ComputerName $XAZDC -BrowserName * | where {$_.Accounts -contains $user})
                if ([bool]($apps -ne $null)) { #Found useraccount assigned to some published resources
                    foreach ($app in $apps) {
                        Remove-XAApplicationAccount -ComputerName $xazdc -BrowserName $app.Browsername -Accounts $user
                    }
                } else {
                    write-verbose "$usr is not assigned to any published applications"
                }
                #Delete user account from AD
                remove-aduser -identity $usr -confirm:$false
                write-verbose "$user removed from $domain"
                $finalout += "$user removed from $domain"
            }
        }
    } Else { #User does not exist - stop and go to next user
        write-verbose "$usr does not exist."
    }
}

#CreateReport
if ($finalout -ne $null) {
    $LogFileFolder = "c:\_scripts"
#    $datetime = get-date -format "MM-dd-yyyy_HH-mm"
    $LogFileName = "Citrix_DeleteUser_Report" + $datetime + ".txt"
    $LogFile = $LogFileFolder + "\" + $LogFilename
    $finalout | ft -auto | out-file $Logfile -append
}

I look forward to any and all comments. I’m sure there are better ways to write some of the above. There’s always more than one way to skin a cat in PowerShell.

Thanks,
Alain