A long time ago in a galaxy far far away I wrote this blog post to log a user off from their vdi session. Today I got an inquiry from Robin Stolpe that he was trying to make it a script with arguments instead if the menu’s but was having some issues with that. This gave me the chance to make it a bit nicer of a script with the option to user username/domain/password as credentials but also a credentialfile , optional forcefully logging off the users and with Robin’s requirements of being able to provide the exact username and the machine that user is working on.
The script can be ran like this:
D:\GIT\Scripts\logoff_user.ps1 -Credentialfile "D:\homelab\creds.xml" -TargetUser "loft.lab\m_wouter" -TargetMachine "lp-001.loft.lab" -ConnectionServerFQDN loftcbr01.loft.lab
or with credentials and the -force parameter
D:\GIT\Scripts\logoff_user.ps1 -TargetUser "loft.lab\m_wouter" -TargetMachine "lp-001.loft.lab" -ConnectionServerFQDN loftcbr01.loft.lab -Username m_wouter -domain loft.lab -password "HAHAHAHA" -force
Now let’s have a look how the script is build.
So I started with the parameters and for that I included 2 parameter sets so you can either choose to have the separate credentials or to use a credentials file.
[CmdletBinding()] Param ( [Parameter(Mandatory=$False, ParameterSetName="separatecredentials", HelpMessage='Enter a username' )] [ValidateNotNullOrEmpty()] [string] $Username, [Parameter(Mandatory=$false, ParameterSetName="separatecredentials", HelpMessage='Domain i.e. loft.lab' )] [string] $Domain, [Parameter(Mandatory=$false, ParameterSetName="separatecredentials", HelpMessage='Password in plain text' )] [string] $Password, [Parameter(Mandatory=$true, HelpMessage='FQDN of the connectionserver' )] [ValidateNotNullOrEmpty()] [string] $ConnectionServerFQDN, [Parameter(Mandatory=$false, ParameterSetName="credsfile", HelpMessage='Path to credentials xml file' )] [ValidateNotNullOrEmpty()] [string] $Credentialfile, [Parameter(Mandatory=$false, HelpMessage='Synchronise the local site only' )] [switch] $Force, [Parameter(Mandatory=$false, HelpMessage='username of the user to logoff (domain\user i.e. loft.lab\user1')] [ValidateNotNullOrEmpty()] [string] $TargetUser, [Parameter(Mandatory=$false, HelpMessage='dns name of the machine the user is on i.d. lp-002.loft.lab')] [string] $TargetMachine )
Than I check if a credential file was supplied and if I can actually import it
if($Credentialfile -and ((test-path $Credentialfile) -eq $true)){ try{ write-host "Using credentialsfile" $credentials=Import-Clixml $Credentialfile $username=($credentials.username).split("\")[1] $domain=($credentials.username).split("\")[0] $secpw=$credentials.password $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secpw) $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } catch{ write-error -Message "Error importing credentials" break } } elseif($Credentials -and ((test-path $credentials) -eq $false)){ write-error "Invalid Path to credentials file" } elseif($username -and $Domain -and $Password){ write-host "Using separate credentials" }
The file doesn’t exist:
Or an error importing the xml (duh, what do you think what happoens when you use a json instead of xml, fool!)
Then it’s a matter of logging in, performing a query and checking if there’s really a session for this user. As you can see I am using machineOrRDSServerDNS so it should also work for RDS sessions.
$hvserver1=connect-hvserver $ConnectionServerFQDN -user $username -domain $domain -password $password $Services1= $hvServer1.ExtensionData $queryService = New-Object VMware.Hv.QueryServiceService $sessionfilterspec = New-Object VMware.Hv.QueryDefinition $sessionfilterspec.queryEntityType = 'SessionLocalSummaryView' $sessionfilter1= New-Object VMware.Hv.QueryFilterEquals $sessionfilter1.membername='namesData.userName' $sessionfilter1.value=$TargetUser $sessionfilter2= New-Object VMware.Hv.QueryFilterEquals $sessionfilter2.membername='namesData.machineOrRDSServerDNS' $sessionfilter2.value=$TargetMachine $sessionfilter=new-object vmware.hv.QueryFilterAnd $sessionfilter.filters=@($sessionfilter1, $sessionfilter2) $sessionfilterspec.filter=$sessionfilter $session=($queryService.QueryService_Create($Services1, $sessionfilterspec)).results $queryService.QueryService_DeleteAll($services1) if($session.count -eq 0){ write-host "No session found for $targetuser on $targetmachine" break }
And last but not least logging the user of with or without the -force option
if($Force){ write-host "Forcefully logging off $targetUser from $targetmachine" $Services1.Session.Session_Logoffforced($session.id) } else{ write-host "Logging off $targetUser from $targetmachine" try{ $Services1.Session.Session_Logoff($session.id) } catch{ write-error "error logging the user off, maybe the sessions was locked. Try with -force" } }
This session was locked
So let’s force that thing
And here’s the entire script but you can also find it on my github.
[CmdletBinding()] Param ( [Parameter(Mandatory=$False, ParameterSetName="separatecredentials", HelpMessage='Enter a username' )] [ValidateNotNullOrEmpty()] [string] $Username, [Parameter(Mandatory=$false, ParameterSetName="separatecredentials", HelpMessage='Domain i.e. loft.lab' )] [string] $Domain, [Parameter(Mandatory=$false, ParameterSetName="separatecredentials", HelpMessage='Password in plain text' )] [string] $Password, [Parameter(Mandatory=$true, HelpMessage='FQDN of the connectionserver' )] [ValidateNotNullOrEmpty()] [string] $ConnectionServerFQDN, [Parameter(Mandatory=$false, ParameterSetName="credsfile", HelpMessage='Path to credentials xml file' )] [ValidateNotNullOrEmpty()] [string] $Credentialfile, [Parameter(Mandatory=$false, HelpMessage='Synchronise the local site only' )] [switch] $Force, [Parameter(Mandatory=$false, HelpMessage='username of the user to logoff (domain\user i.e. loft.lab\user1')] [ValidateNotNullOrEmpty()] [string] $TargetUser, [Parameter(Mandatory=$false, HelpMessage='dns name of the machine the user is on i.d. lp-002.loft.lab')] [string] $TargetMachine ) if($Credentialfile -and ((test-path $Credentialfile) -eq $true)){ try{ write-host "Using credentialsfile" $credentials=Import-Clixml $Credentialfile $username=($credentials.username).split("\")[1] $domain=($credentials.username).split("\")[0] $secpw=$credentials.password $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secpw) $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) } catch{ write-error -Message "Error importing credentials" break } } elseif($Credentials -and ((test-path $credentials) -eq $false)){ write-error "Invalid Path to credentials file" break } elseif($username -and $Domain -and $Password){ write-host "Using separate credentials" } $hvserver1=connect-hvserver $ConnectionServerFQDN -user $username -domain $domain -password $password $Services1= $hvServer1.ExtensionData $queryService = New-Object VMware.Hv.QueryServiceService $sessionfilterspec = New-Object VMware.Hv.QueryDefinition $sessionfilterspec.queryEntityType = 'SessionLocalSummaryView' $sessionfilter1= New-Object VMware.Hv.QueryFilterEquals $sessionfilter1.membername='namesData.userName' $sessionfilter1.value=$TargetUser $sessionfilter2= New-Object VMware.Hv.QueryFilterEquals $sessionfilter2.membername='namesData.machineOrRDSServerDNS' $sessionfilter2.value=$TargetMachine $sessionfilter=new-object vmware.hv.QueryFilterAnd $sessionfilter.filters=@($sessionfilter1, $sessionfilter2) $sessionfilterspec.filter=$sessionfilter $session=($queryService.QueryService_Create($Services1, $sessionfilterspec)).results $queryService.QueryService_DeleteAll($services1) if($session.count -eq 0){ write-host "No session found for $targetuser on $targetmachine" break } if($Force){ write-host "Forcefully logging off $targetUser from $targetmachine" $Services1.Session.Session_Logoffforced($session.id) } else{ write-host "Logging off $targetUser from $targetmachine" try{ $Services1.Session.Session_Logoff($session.id) } catch{ write-error "error logging the user off, maybe the sessions was locked. Try with -force" } }