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 Stop-RDSSession {


<#
    .SYNOPSIS
    Stop-RDSSession [[-Broker] <String[]>] [-Credential] [-Force]
 
    .DESCRIPTION
    Initiate user session logoff or reset.
  
    .PARAMETER Broker
    -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com.
 
    .PARAMETER Credential
    -Credential [-C] - Credentials switch. Enforce credential form.
 
    .PARAMETER Force
    -Force [-F] - Force logoff (reset session). Use this option only if logoff does not work. It can corrupt user profile.
 
    .EXAMPLE
    # Starts console, initiates logoff:
    Stop-RDSSession -B RDCB1.domain.com
 
    .EXAMPLE
    # Starts console with credentials prompt, initiates session reset:
    Stop-RDSSession -Broker RDCB1.domain.com -C -F
 
    .LINK
    http://www.heavenbay.ru/app/sintez/help/module/rds#logoff
#>



    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [alias("B")]
        [string]$Broker,
    
        [alias("C")]
        [switch]$Credential,

        [alias("F")]
        [switch]$Force

    )

    if ($Credential -eq $true) {
        [PSCredential]$Credential = Get-Credential
    } else {
        [PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty
    }

    If ($Force -eq $true) {
        $cmd = "rwinsta"
    } else {
        $cmd = "logoff"
    }

    $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 $cmd "$SID /SERVER:$Host" -WindowStyle Hidden
            }
            Else {
                foreach($S in $Sessions) {
                    [int]$SID = $S.Split(";")[1]
                    [string]$Host = $S.Split(";")[2]
                    start-process $cmd "$SID /SERVER:$Host" -WindowStyle Hidden
                }
            }
        }
    }

    CreateUsersList $Credential $Broker $newCol
}

function Set-RDSDrainMode {


<#
    .SYNOPSIS
    Set-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
    # Enable drain mode for selected servers:
    Set-RDSDrainMode -B RDCB1.domain.com -E
 
    .LINK
    http://www.heavenbay.ru/app/sintez/help/module/rds#drainmode
#>



    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [alias("B")]
        [string]$Broker,
    
        [alias("C")]
        [switch]$Credential,

        [parameter(ParameterSetName = "Enable")]
        [alias("E")]
        [switch]$Enable,

        [parameter(ParameterSetName = "Disable")]
        [alias("D")]
        [switch]$Disable,

        [parameter(ParameterSetName = "Reboot")]
        [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"}
            2 {$rez = "ON"}
            1 {$rez = "ON, REBOOT"}
            default {$rez = "Something goes wrong."}
        }
        return $rez
    }

    if ($Disable) {
        [int]$DSwitch = "0"
    }
    ElseIf ($Enable) {
        [int]$DSwitch = "2"
    }
    ElseIf ($Reboot) {
        [int]$DSwitch = "1"
    }
    ElseIf (!($Disable -or $Enable -or $Reboot)) {
        Write-Host "Nothing selected. Choose one:"
        Write-Host "Set-RDSDrainMode -B BROKER -E"
        Write-Host "Set-RDSDrainMode -B BROKER -D"
        Write-Host "Set-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) {
                    [string]$DrainMode = $H.Split(";")[0]
                    [string]$Host = $H.Split(";")[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
                    }
                }
            }
        }
    }

    $($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
}

function Send-RDSPrivateMessage {


<#
    .SYNOPSIS
    Send-RDSPrivateMessage [[-Broker] <String[]>] [[-Message] <String[]>] [-Credential]
 
    .DESCRIPTION
    Sends private message to users.
  
    .PARAMETER Broker
    -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com.
 
    .PARAMETER Message
    -Message [-M] - Message text.
 
    .PARAMETER Credential
    -Credential [-C] - Credentials switch. Enforce credential form.
 
    .EXAMPLE
    # Send private message to selected users:
    Send-RDSPrivateMessage -B rdcb.domain.com -M "Hey! Big bro is watching you :)"
 
    .LINK
    http://www.heavenbay.ru/app/sintez/help/module/rds#pmessage
#>



    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [alias("B")]
        [string]$Broker,
    
        [Parameter(Mandatory=$true)]
        [alias("M")]
        [string]$Message,

        [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 msg "$SID /SERVER:$Host /TIME:180 $Message" -WindowStyle Hidden
            }
            Else {
                foreach($S in $Sessions) {
                    [int]$SID = $S.Split(";")[1]
                    [string]$Host = $S.Split(";")[2]
                    start-process msg "$SID /SERVER:$Host /TIME:180 $Message" -WindowStyle Hidden
                }
            }
        }
    }

    CreateUsersList $Credential $Broker $newCol

}

function Send-RDSMassiveMessage {


<#
    .SYNOPSIS
    Send-RDSMassiveMessage [[-Broker] <String[]>] [[-Message] <String[]>] [-Credential]
 
    .DESCRIPTION
    Sends massive message to ALL users on server.
  
    .PARAMETER Broker
    -Broker [-B] - RDCB FQDN. Ex.: RDCB1.domain.com.
 
    .PARAMETER Message
    -Message [-M] - Message text.
 
    .PARAMETER Credential
    -Credential [-C] - Credentials switch. Enforce credential form.
 
    .EXAMPLE
    # Send massive message to ALL users on selected servers:
    Send-RDSMassiveMessage -B rdcb.domain.com -M "Hey! Big bro is watching you :)"
 
    .LINK
    http://www.heavenbay.ru/app/sintez/help/module/rds#mmessage
#>



    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [alias("B")]
        [string]$Broker,
    
        [Parameter(Mandatory=$true)]
        [alias("M")]
        [string]$Message,

        [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)]
            [string[]]$Host
        )

        Begin {
            $arHost = @()
        }
        Process {
            $arHost += "$Host"
        }
        End {
            If ($arHost.Count -le 0) {
                Write-Host ""
                Write-Host "No sessions. Do nothing."
                Write-Host ""
            }
            ElseIf ($arHost.Count -eq 1) {
                start-process msg "* /SERVER:$Host /TIME:180 $Message" -WindowStyle Hidden
            }
            Else {
                foreach($Host in $arHost) {
                    start-process msg "* /SERVER:$Host /TIME:180 $Message" -WindowStyle Hidden
                }
            }
        }
    }

    gwmi -ComputerName $Broker -Credential $Credential -Query "SELECT ServerName,NumberOfSessions FROM Win32_SessionDirectoryServer" `
    | select `
    @{Label="Host";Expression={$_.ServerName}},`
    @{Label="Collections";Expression={$newCol[$_.ServerName]}},`
    @{Label="Sessions";Expression={$_.NumberOfSessions}} `
    | sort Host `
    | Out-GridView -PassThru -Title "Massive Message - Select Servers" `
    | SessionAction

}

Export-ModuleMember -Function "Install-RDSServer", "Install-RDSClient", "Start-RDSConnect","Stop-RDSSession","Set-RDSDrainMode","Send-RDSPrivateMessage","Send-RDSMassiveMessage"