Sintez.Module.RDS.psm1
################################################################################ # Copyright 2017 Sergey Rusak, Heavenbay.ru # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ # Reset vars: [string]$Broker = "" [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty # Associate digitals in user session status to words: function getStateName([int]$StateId) { [string]$rez = "###"; switch ($StateId) { 0 {$rez = "Active"} 1 {$rez = "Active, Minimized"} 2 {$rez = "Query"} 3 {$rez = "Shadow"} 4 {$rez = "Disconnected"} 5 {$rez = "Idle"} 6 {$rez = "Listen"} 7 {$rez = "Reset"} 8 {$rez = "Down"} 9 {$rez = "Init"} default {$rez = "Invalid State"} } return $rez } # Associate collection names from aliases to fullnames and make table with host/fullname. function SelectCollections ([PSCredential]$Credential,[string]$Broker) { $col = @{} $newCol = @{} Try { Get-WmiObject -Credential $Credential -ComputerName $Broker -Query "SELECT Alias, Name FROM Win32_RDSHCollection" -Namespace root\CIMv2\rdms | %{$col.Add($_.alias, $_.name)} } Catch [System.UnauthorizedAccessException] { Write-Host "Access to a remote broker is denided. Start cmdlet with a key '-C' to set the right credentials." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } Catch [System.Runtime.InteropServices.COMException] { Write-Host "Broker is unavailable. Probably wrong IP/FQDN or firewall issues." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } Get-WmiObject -Credential $Credential -ComputerName $Broker -Query "SELECT Name, CollectionAlias FROM Win32_RDSHServer" -Namespace root\CIMv2\rdms | %{$newCol.Add(($_.Name),$col[$_.CollectionAlias])} return $newCol } # Create user list table with GUI function CreateUsersList ([PSCredential]$Credential,[string]$Broker,[array]$newCol) { Get-WmiObject -Credential $Credential -ComputerName $Broker -Namespace "root\CIMv2" -Query "SELECT UserName,IdleTime,CreateTime,HostServer,DomainName,SessionState,UnifiedSessionID FROM Win32_SessionDirectorySessionEx" ` | Select UserName,` @{Label="SID";Expression={$_.UnifiedSessionID}},` @{Label="Host";Expression={$_.HostServer}},` @{Label="Activity";Expression={getStateName($_.SessionState)}},` @{Label="Collection";Expression={$newCol[$_.HostServer]}},` @{Label="Creation Time";Expression={[datetime]::ParseExact(($_.CreateTime -replace "\..*",""),"yyyyMMddHHmmss", $null)}},` @{Label="Domain";Expression={$_.DomainName}},` @{Label="Idle";Expression={"{0:N0}" -f ([timespan]::frommilliseconds($_.IdleTime)).TotalMinutes}} ` | Sort UserName ` | Out-GridView -PassThru -Title "RDS Sessions" ` | SessionAction } function Install-RDSServer { <# .Synopsis Install-RDSServer [[-RDSH] <Array[]>] [[-Domain] <String[]>] [[-Group] <String[]>] [-View] [-Remove] [-Credential] .Description Add/Remove user/group to RDP-Tcp administrator group settings on server side (ONLY RDSH servers). By default only users from Administrators group are able to make changes on RDP-Tcp connections. If you want to grant access to another group you have to create a new group in AD and add it to Security ACLs. This script will add it for you. This script MUST be started as local Administrator. .Parameter RDSH -RDSH [-SH] - Execute commands on the specified computers. The default is the local computer. Type the NetBIOS name, an IP address, or a fully qualified domain name (FQDN) of a remote computer. To specify the local computer, type the computer name, a dot (.), or localhost. .Parameter Domain -Domain [-D] - NetBIOS domain name. For domain.com it should be just DOMAIN. .Parameter Group -Group [-G] - Group of users who allow to connect (shadow) to the user session. .Parameter View -View [-V] - Show all groups from RDP-Tcp security settings tab. .Parameter Remove -Remove [-R] - Clear all custom groups in RDP-Tcp security ACLs (reset param). .Parameter Credential -Credential [-C] - Credentials switch. Enforce credential form. .Example # Add RDS security group with administrator rights (Shadow, Logoff, Disconnect and etc.) to local RDSH server. If you add group you also have to set DOMAIN param. And vice versa: Install-RDSServer -Domain DOMAIN -Group rds_support_team .Example # As in example #1 we add RDS security group to remote RDSH servers: Install-RDSServer -SH RDSH1,RDSH2,RDSH3 -Domain DOMAIN -Group rds_support_team .Example # Clear all custom groups in RDP security settings on local RDSH server: Install-RDSServer -R .Example # Show all groups in RDP-Tcp security settings on remote RDSH servers. Param "-C" tells cmdlet launch credential prompt window before script will continue: Install-RDSServer -View -SH RDSH1,RDSH2,RDSH3 -C .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#installserver #> [CmdletBinding()] Param( [alias("SH")] [array]$RDSH, [alias("D")] [string]$Domain, [alias("G")] [string]$Group, [alias("R")] [switch]$Remove, [alias("V")] [switch]$View, [alias("C")] [switch]$Credential ) $ErrorActionPreference = "stop" if (!$RDSH) {[array]$RDSH = $ENV:COMPUTERNAME} if ($Credential -eq $true) { [PSCredential]$Credential = Get-Credential } else { [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty } if ($Remove -eq $true) { foreach ($SH in $RDSH) { Write-Host "" Write-Host "Server is $SH`:" Try { ((gwmi -ComputerName $SH -Credential $Credential -Namespace "root\CIMV2\TerminalServices" -Class Win32_TSPermissionsSetting).where({$_.TerminalName -eq "RDP-Tcp"})).RestoreDefaults() | Out-Null If (!$Error) {Write-Host "Done" -BackgroundColor Black -ForegroundColor Green} } Catch [System.Management.Automation.MethodInvocationException] { Write-Host "Check that RDS-RD-Server role is installed on this PC." -BackgroundColor Black -ForegroundColor Red } Catch [System.Runtime.InteropServices.COMException] { Write-Host "RDSH is unavailable. Probably wrong IP/FQDN or firewall issues." -BackgroundColor Black -ForegroundColor Red } Catch [System.UnauthorizedAccessException] { Write-Host "Access to a remote broker is denided. Start cmdlet with a key '-C' to set the right credentials." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } } Write-Host "" Write-Host "Complited!" Write-Host "" break } if ($View -eq $true) { foreach ($SH in $RDSH) { Write-Host "" Write-Host "Server is $SH`:" Try { $v = ((gwmi -ComputerName $SH -Credential $Credential -Namespace "root\CIMV2\TerminalServices" -Class Win32_TSPermissionsSetting).where({$_.TerminalName -eq "RDP-Tcp"})).StringSecurityDescriptor [regex]::matches($v, "S-1-5-\d+-\d+-\d+-\d+-\d+").Groups.Value | %{Write-Host (New-Object System.Security.Principal.SecurityIdentifier($_)).Translate([System.Security.Principal.NTAccount]).Value -ForegroundColor Yellow -BackgroundColor Black} } Catch [System.Management.Automation.PSArgumentException] { Write-Host "There are no groups." -ForegroundColor Yellow -BackgroundColor Black } Catch [System.Runtime.InteropServices.COMException] { Write-Host "RDSH is unavailable. Probably wrong IP/FQDN or firewall issues." -BackgroundColor Black -ForegroundColor Red } Catch [System.UnauthorizedAccessException] { Write-Host "Access to a remote broker is denided. Start cmdlet with a key '-C' to set the right credentials." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } } Write-Host "" Write-Host "Complited!" Write-Host "" break } $Group = $Domain + "\" + $Group foreach ($SH in $RDSH) { Write-Host "" Write-Host "Server is $SH`:" Try { ((gwmi -ComputerName $SH -Credential $Credential -Namespace "root\CIMV2\TerminalServices" -Class Win32_TSPermissionsSetting).where({$_.TerminalName -eq "RDP-Tcp"})).AddAccount($Group,2) | Out-Null If (!$Error) {Write-Host "Done" -BackgroundColor Black -ForegroundColor Green} } Catch [System.Runtime.InteropServices.COMException] { Write-Host "RDSH is unavailable. Probably wrong IP/FQDN or firewall issues." -BackgroundColor Black -ForegroundColor Red } Catch [System.Management.Automation.MethodInvocationException] { Write-Host "Wrong domain/group. Please restart script with right params." -ForegroundColor Red -BackgroundColor Black Break } Catch [System.UnauthorizedAccessException] { Write-Host "Access to a remote broker is denided. Start cmdlet with a key '-C' to set the right credentials." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } } Write-Host "" Write-Host "Complited!" Write-Host "" } function Install-RDSClient { <# .Synopsis Install-RDSClient [-Remove] .Description Add/Remove registry tree "rdsmc" and .js file (WINDIR) for SINTEZ App RDS module on client PC. This script MUST be started as local Administrator. .Parameter Remove -Remove [-R] - Uninstall changes. .Example # Install RDS module settings on support team PC: Install-RDSClient .Example # Remove all RDS module client-side components: Install-RDSClient -R .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#installclient #> [CmdletBinding()] Param( [switch]$Remove ) $win = $ENV:SYSTEMROOT $winjs = $win -replace "\\","\\" $RegPath = "HKLM:\SOFTWARE\Classes\rdsmc" $ErrorActionPreference = "stop" if ($Remove -eq $true) { Try { Remove-Item -Path $RegPath -Force -Recurse } Catch [System.Security.SecurityException] { Write-Host "Please, start new powershell session with local administrator rights and execute the cmdlet again." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } Catch [System.Management.Automation.ItemNotFoundException] { Write-Host "Registry: no item. Skipped..." -BackgroundColor Black -ForegroundColor Yellow } Try { Remove-Item -Path "$win\SnitezRDSMC.js" -Force } Catch [System.Management.Automation.ItemNotFoundException] { Write-Host "JS file: no item. Skipped..." -BackgroundColor Black -ForegroundColor Yellow } Write-Host "" Write-Host "Complited" -BackgroundColor Black -ForegroundColor Green Write-Host "" break } Try { New-Item -Path $RegPath -Value "URL:Remote Desktop Connection Management Console" -Force | Out-Null } Catch [System.Security.SecurityException] { Write-Host "Please, start new powershell session with local administrator rights and execute the cmdlet again." -BackgroundColor Black -ForegroundColor Red Write-Host "" Break } New-ItemProperty -Path $RegPath -Name "URL Protocol" -Force | Out-Null New-Item -Path "$RegPath\DefaultIcon" -Value "$win\System32\mstsc.exe" -Force | Out-Null New-Item -Path "$RegPath\shell\open\command" -Value "wscript.exe $win\SnitezRDSMC.js %1" -Force | Out-Null [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(" dmFyIHN0ckFyZ3M9KFdTY3JpcHQuQXJndW1lbnRzKDApKQp2YXIgcHJlZml4PSdyZHNtYzovLycKc3RyQXJncz1zdHJBcmdzLnJlcGxhY2UocHJlZml4LCAn JykKc3RyQXJncz1zdHJBcmdzLnNwbGl0KCcvJykKdmFyIHNoZWxsID0gbmV3IEFjdGl2ZVhPYmplY3QoIldTY3JpcHQuU2hlbGwiKQppZiAoc3RyQXJnc1sw XSA9PSAic2hhZG93IikgewogICAgdmFyIGFwcD0nJVdJTkRJUiVcXHN5c3RlbTMyXFxtc3RzYy5leGUnCiAgICBzaGVsbC5FeGVjKGFwcCArICIgL3Y6IiAr IHN0ckFyZ3NbMV0gKyAiIC9zaGFkb3c6IiArIHN0ckFyZ3NbMl0gKyAiIC9jb250cm9sIC9ub2NvbnNlbnRwcm9tcHQiKQp9IGVsc2UgaWYgKHN0ckFyZ3Nb MF0gPT0gIm1zcmEiKSB7CiAgICB2YXIgYXBwPSclV0lORElSJVxcc3lzV09XNjRcXG1zcmEuZXhlJzsKICAgIHNoZWxsLkV4ZWMoYXBwICsgIiAvb2ZmZXJS QSAiICsgc3RyQXJnc1sxXSkKfSBlbHNlIGlmIChzdHJBcmdzWzBdID09ICJSRFAiKSB7CiAgICB2YXIgYXBwPSclV0lORElSJVxcc3lzdGVtMzJcXG1zdHNj LmV4ZSc7CiAgICBzaGVsbC5FeGVjKGFwcCArICIgL3Y6IiArIHN0ckFyZ3NbMV0pCn0gZWxzZSB7CiAgICBhbGVydCgnU29tZXRoaW5nIGdvZXMgd3Jvbmch Jyk7Cn0K ")) | Out-File -FilePath "$win\SnitezRDSMC.js" -Force Write-Host "" Write-Host "Complited!" -BackgroundColor Black -ForegroundColor Green Write-Host "" } function Start-RDSConnect { <# .Synopsis Start-RDSConnect [[-Broker] <String[]>] [-Credential] .Description Gets user list, starts RDP connection (shadow). .Parameter Broker -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com. .Parameter Credential -Credential [-C] - Credentials switch. Enforce credential form. .Example # Start console: Start-RDSConnect -B RDCB1.domain.com .Example # Start console with credentials prompt: Start-RDSConnect -Broker RDCB1.domain.com -C .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#connect #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [alias("B")] [string]$Broker, [alias("C")] [switch]$Credential ) if ($Credential -eq $true) { [PSCredential]$Credential = Get-Credential } else { [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty } $newCol = SelectCollections $Credential $Broker function SessionAction { Param ( [parameter(ValueFromPipelineByPropertyName)] [int[]]$SID, [parameter(ValueFromPipelineByPropertyName)] [string[]]$UserName, [parameter(ValueFromPipelineByPropertyName)] [string[]]$Host ) Begin { $Sessions = @() } Process { $Sessions += "$UserName;$SID;$Host" } End { If ($Sessions.Count -le 0) { Write-Host "" Write-Host "No sessions. Do nothing." Write-Host "" } ElseIf ($Sessions.Count -eq 1) { start-process rdsmc://shadow/$Host/$SID } Else { Write-Host "It's a bad idea to start 100500 RDP sessions. I limit them to 2." -BackgroundColor Black -ForegroundColor Yellow Write-Host "" foreach($S in $Sessions[0..1]) { [int]$SID = $S.Split(";")[1] [string]$Host = $S.Split(";")[2] start-process rdsmc://shadow/$Host/$SID } } } } CreateUsersList $Credential $Broker $newCol } function Start-RDSLogoff { <# .Synopsis Start-RDSLogoff [[-Broker] <String[]>] [-Credential] .Description Initiate user logoff. .Parameter Broker -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com. .Parameter Credential -Credential [-C] - Credentials switch. Enforce credential form. .Example # Start console: Start-RDSLogoff -B RDCB1.domain.com .Example # Start console with credentials prompt: Start-RDSLogoff -Broker RDCB1.domain.com -C .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#logoff #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [alias("B")] [string]$Broker, [alias("C")] [switch]$Credential ) if ($Credential -eq $true) { [PSCredential]$Credential = Get-Credential } else { [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty } $newCol = SelectCollections $Credential $Broker function SessionAction { Param ( [parameter(ValueFromPipelineByPropertyName)] [int[]]$SID, [parameter(ValueFromPipelineByPropertyName)] [string[]]$UserName, [parameter(ValueFromPipelineByPropertyName)] [string[]]$Host ) Begin { $Sessions = @() } Process { $Sessions += "$UserName;$SID;$Host" } End { If ($Sessions.Count -le 0) { Write-Host "" Write-Host "No sessions. Do nothing." Write-Host "" } ElseIf ($Sessions.Count -eq 1) { start-process logoff "$SID /SERVER:$Host" -WindowStyle Hidden } Else { foreach($S in $Sessions) { [int]$SID = $S.Split(";")[1] [string]$Host = $S.Split(";")[2] start-process logoff "$SID /SERVER:$Host" -WindowStyle Hidden } } } } CreateUsersList $Credential $Broker $newCol } function Start-RDSReset { <# .Synopsis Start-RDSReset [[-Broker] <String[]>] [-Credential] .Description Reset user session. Use this cmdlet only if Start-RDSLogoff does not work. It can corrupt user profile. .Parameter Broker -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com. .Parameter Credential -Credential [-C] - Credentials switch. Enforce credential form. .Example # Start console: Start-RDSReset -B RDCB1.domain.com .Example # Start console with credentials prompt: Start-RDSReset -Broker RDCB1.domain.com -C .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#logoff #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [alias("B")] [string]$Broker, [alias("C")] [switch]$Credential ) if ($Credential -eq $true) { [PSCredential]$Credential = Get-Credential } else { [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty } $newCol = SelectCollections $Credential $Broker function SessionAction { Param ( [parameter(ValueFromPipelineByPropertyName)] [int[]]$SID, [parameter(ValueFromPipelineByPropertyName)] [string[]]$UserName, [parameter(ValueFromPipelineByPropertyName)] [string[]]$Host ) Begin { $Sessions = @() } Process { $Sessions += "$UserName;$SID;$Host" } End { If ($Sessions.Count -le 0) { Write-Host "" Write-Host "No sessions. Do nothing." Write-Host "" } ElseIf ($Sessions.Count -eq 1) { start-process rwinsta "$SID /SERVER:$Host" -WindowStyle Hidden } Else { foreach($S in $Sessions) { [int]$SID = $S.Split(";")[1] [string]$Host = $S.Split(";")[2] start-process rwinsta "$SID /SERVER:$Host" -WindowStyle Hidden } } } } CreateUsersList $Credential $Broker $newCol } function Start-RDSDrainMode { <# .Synopsis Start-RDSDrainMode [[-Broker] <String[]>] [-Credential] [-Enable] [-Disable] [-Reboot] .Description Gets and sets drain mode of RDSH servers. You MUST choose ONE of [-E | -D | -R]. .Parameter Broker -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com. .Parameter Credential -Credential [-C] - Credentials switch. Enforce credential form. .Parameter Enable -Enable [-E] - Enable permament drain mode: allow incoming reconnections but prohibit new connections. .Parameter Disable -Disable [-D] - Disable drain mode: allow all connections. .Parameter Reboot -Reboot [-R] - Enable drain mode until reboot: allow incoming reconnections but until reboot prohibit new connections. .Example # Get servers list with drain mode statuses and enable drain mode on seleted RDSH: Start-RDSReset -B RDCB1.domain.com .LINK http://www.heavenbay.ru/app/sintez/help/module/rds#drainmode #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [alias("B")] [string]$Broker, [alias("C")] [switch]$Credential, [alias("E")] [switch]$Enable, [alias("D")] [switch]$Disable, [alias("R")] [switch]$Reboot ) if ($Credential -eq $true) { [PSCredential]$Credential = Get-Credential } else { [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty } $newCol = SelectCollections $Credential $Broker function getDrainMode([int]$DrainModeId) { [string]$rez = "###"; switch ($DrainModeId) { 0 {$rez = "OFF"} 1 {$rez = "ON"} 2 {$rez = "ON, REBOOT"} default {$rez = "Something goes wrong."} } return $rez } if ($Disable) { [int]$DSwitch = "0" } ElseIf ($Enable) { [int]$DSwitch = "1" } ElseIf ($Reboot) { [int]$DSwitch = "2" } ElseIf (!($Disable -or $Enable -or $Reboot)) { Write-Host "Nothing has been choosen." Write-Host "Start-RDSDrainMode -B BROKER -E" Write-Host "Start-RDSDrainMode -B BROKER -D" Write-Host "Start-RDSDrainMode -B BROKER -R" Break } function SessionAction { Param ( [parameter(ValueFromPipelineByPropertyName)] [string[]]$Host, [parameter(ValueFromPipelineByPropertyName)] [string[]]$DrainMode ) Begin { $Hosts = @() } Process { $Hosts += "$DrainMode;$Host" } End { If ($Hosts.Count -le 0) { Write-Host "" Write-Host "No sessions. Do nothing." Write-Host "" } ElseIf ($Hosts.Count -eq 1) { try { Set-WmiInstance -Credential $Credential -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -Arguments @{SessionBrokerDrainMode=$DSwitch} -ComputerName $Host | Out-Null } Catch [System.Runtime.InteropServices.COMException] { Write-Host "Unable to connect to server: $Host. Skip it." -BackgroundColor Black -ForegroundColor Red } } Else { foreach($H in $Hosts) { [int]$SID = $H.Split(";")[1] [string]$Host = $H.Split(";")[2] try { Set-WmiInstance -Credential $Credential -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -Arguments @{SessionBrokerDrainMode=$DSwitch} -ComputerName $Host | Out-Null } Catch [System.Runtime.InteropServices.COMException] { Write-Host "Unable to connect to server: $Host. Skip it." -BackgroundColor Black -ForegroundColor Red } } } } } $($newCol.Keys) | %{ $RDSH = $_; Get-WmiObject -Credential $Credential -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $RDSH ` | select ` @{Label="Host";Expression={$RDSH}}, ` @{Label="Collection";Expression={$newCol[$RDSH]}}, ` @{Label="DrainMode";Expression={getDrainMode($_.SessionBrokerDrainMode)}}} ` | Sort "Host" ` | Out-GridView -PassThru -Title "RDSH Drain Mode Status" ` | SessionAction } Export-ModuleMember -Function "Install-RDSServer", "Install-RDSClient", "Start-RDSConnect","Start-RDSLogoff","Start-RDSReset","Start-RDSDrainMode" |