Monitor current RDG connections

To obtain the same output as the remote desktop gateway snap-in (tsgateway.msc), aka the RD Gateway Manager, you can use the following function that accepts server names and credentials as input:

Function Get-RDGConnections            
{            
    [CmdletBinding(DefaultParameterSetName='Computer', SupportsTransactions=$false)]            
    param(            
       [Parameter(ParameterSetName='Computer', ValueFromPipeline=$true, Mandatory=$false, Position=0)]            
        [system.string[]]${ComputerName},            
            
       [Parameter(ParameterSetName='Computer', ValueFromPipeline=$false, Mandatory=$false, Position=1)]            
        [System.Management.Automation.PSCredential]$Credential = $null            
    )            
            
    begin            
    {            
            # Check if we've got the value from the pipeline            
            $direct = $PSBoundParameters.ContainsKey('ComputerName')            
            
            # Build a hashtable for splatting            
            $otherparams = @{}            
            if ($credential)            
            {            
                $otherparams += @{Credential = $Credential}            
            }            
            
    }            
    process            
    {            
        $resultsar = @()            
        foreach ($computer in $ComputerName)            
        {            
            $allRDGconnections = @()            
            # Write-Verbose -Message "Dealing with $computer" -Verbose:$true            
            try            
            {            
                # http://social.technet.microsoft.com/Forums/en-US/winserverpowershell/thread/85e0c2bf-abca-4cf9-9355-cb066344a7d5/            
                $allRDGconnections = @(Get-WmiObject -class "Win32_TSGatewayConnection" -namespace "root\cimv2\TerminalServices" -ComputerName $computer -Authentication 6 -ErrorAction Stop @otherparams)            
            }             
            catch            
            {            
                # WMI was unable to retrieve the information            
                switch ($_)            
                {            
                    # sort out most common errors and return qualified information            
                    {          $_.Exception.ErrorCode -eq 0x800706ba} { $reason = 'Unavailable (offline, firewall)' }            
                    {          $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } { $reason = 'Access denied' }            
                    # {          $_.Exception.ErrorCode -eq 0x80070005} { $reason = 'Access denied' }            
                    # return all other non-common errors            
                    default { $reason = $_.Exception.Message }            
                } # end of switch            
                if ($direct) {Write-Host -ForegroundColor Red -Object ("Failed to connected to $computer because $reason")  }            
            } # end of catch            
            if ($allRDGconnections.Count -ne 0)            
            {            
                foreach ($item in $allRDGconnections)            
                {            
                    # Write-Verbose -Message "Adding $($item.ConnectedResource)" -Verbose:$true            
                    $resultsar += New-Object -TypeName PSObject -Property @{            
                        ViaRDGServer = $computer            
                        ConnectionID  = $item.ConnectionKey            
                        UserName = $item.FullUserName            
                        TargetComputer = $item.ConnectedResource            
                        UserID = $item.UserName            
                        ClientIPAddress = $item.ClientAddress               
                        ConnectionDuration = [System.Management.ManagementDateTimeConverter]::ToTimeSpan(($item.ConnectionDuration))            
                        ConnectedOn = $item.ConvertToDateTime($item.ConnectedTime)            
                        IdleTime = [System.Management.ManagementDateTimeConverter]::ToTimeSpan(($item.IdleTime))            
                        TargetPort = $item.ConnectedPort            
                        KilobytesReceived= $item.NumberOfKilobytesReceived            
                        KilobytesSent = $item.NumberOfKilobytesSent            
                     }            
                }            
            } else {            
                if ($direct) {Write-Host -ForegroundColor Yellow -Object ("No body connected on $computer")}            
            }            
        } # end of foreach $computer            
        # Output results            
        if ($resultsar -ne $null)            
        {            
            return $resultsar            
        }            
    }            
    end {}            
} # end of function

IOW, you can do now for example:

Get-RDGConnections -ComputerName "RDGServer1","IP.of.RDG.server2" | Format-Table -AutoSize -Wrap -Property *

Or

"RDGServer1","IP.of.RDG.server2" | Get-RDGConnections -Credential (Get-Credential) | Format-Table -AutoSize -Wrap -Property *

Audit RDG Connections

Don’t get confused, RDG in the acronym for “Remote Desktop Gateway“, formerly known as TSG (Terminal Service Gateway) that is the endpoint of encapsulated remote desktop’s connections through a HTTPS tunnel. To get the history of remote desktop accesses via the gateway, you can do:

# Extract info from logs            
$RDGevents = Get-WinEvent -FilterHashtable @{Logname = "Microsoft-Windows-TerminalServices-Gateway/Operational" ; ID = "303","302","202","307"} -ErrorAction SilentlyContinue            
            
$eventsar = @()            
foreach ($event in $RDGevents)            
{            
    $eventtype = $type = $null            
    # http://technet.microsoft.com/en-us/library/ee891388%28WS.10%29.aspx            
    switch ($event.ID)            
    {            
     303 { $eventtype = "disconnect" }            
     307 { $eventtype = "disconnect at timeout" }            
     202 { $eventtype = "disconnected by admin" }            
     302 { $eventtype = "connect" }            
     } # end of switch                
            
            
    $eventsar += New-Object -TypeName PSObject -Property @{            
                    RDGServerName = $env:computername            
                    UserName = $event.Properties[0].Value            
                    IpAddress = [net.ipaddress]$event.Properties[1].Value            
                    Resource = $Event.Properties[3].Value            
                    TimeCreated = $event.TimeCreated            
                    Result = $eventtype            
                }            
}                        
            
# Display results            
$eventsar | Sort-Object -Descending:$false -Property TimeCreated | Format-Table -AutoSize -Wrap

Audit RDP connections

As you may know, it’s quite urgent to apply MS12-020 and/or at least to apply mitigations.

You may be interested in tracking RDP logons as well. As far as I know there are actually 2 ways to achieve this depending on your audit policy settings.

By default, you can do:

# Extract info from logs            
$allRDPevents = Get-WinEvent -FilterHashtable @{Logname = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational" ; ID = 1149,1150,1148} -ErrorAction SilentlyContinue            
            
$RDPevents = @()              
foreach ($event in $allRDPevents)            
{            
    $result = $type = $null            
    # http://technet.microsoft.com/en-us/library/ee891195%28v=ws.10%29.aspx            
    switch ($event.ID)            
    {            
        1148 { $result = "failed"    }            
        1149 { $result = "succeeded" }            
        1150 { $result =  "merged"   }            
    }            
    $RDPevents += New-Object -TypeName PSObject -Property @{            
                    ComputerName = $env:computername            
                    User = $event.Properties[0].Value            
                    Domain = $event.Properties[1].Value            
                    SourceNetworkAddress = [net.ipaddress]$Event.Properties[2].Value            
                    TimeCreated = $event.TimeCreated            
                    Result = $result            
                }            
}            
            
# Display results            
$RDPevents | Sort-Object -Descending:$true -Property TimeCreated | Format-Table -AutoSize -Wrap            

If you enable ‘Audit Other Logon/Logoff events’ in your auditing policy settings you can then get additional info like this:

# More filtering can be added to this here-string XML             
$xml=@"

  
    
	*[System
	   [
	    (Level=1  or Level=2 or Level=3 or Level=4 or Level=0 or Level=5)
	    and ( EventID=4778 or EventID=4779 )
	   ]
	]
   
  

"@            
            
$events = Get-WinEvent -FilterXml $xml -ErrorAction SilentlyContinue            
            
$RDPevents = @()              
foreach ($event in $events)            
{            
    # Define a pattern that matches an IP address            
    $pattern = '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'            
    # Reset variable            
    $IP = $type = $null            
            
    switch($Event.Properties[5].Value)            
    {            
        { $_ -ieq "LOCAL"    } { $IP = "Local" }            
        { $_ -match $pattern } { $IP = [system.net.ipaddress]$Event.Properties[5].Value }            
        default                { $IP = $Event.Properties[5].Value }            
    }            
            
    switch ($event.ID)            
    {            
        4778 { $type = "Session Reconnected"  }            
        4779 { $type = "Session Disconnected" }            
    }            
            
    $RDPevents += New-Object -TypeName PSObject -Property @{            
                    ComputerName = $env:computername            
                    User = $event.Properties[0].Value            
                    Domain = $event.Properties[1].Value            
                    SourceNetworkAddress = $IP            
                    SourceComputerName = $event.Properties[4].Value            
                    RDPSessionName = $event.Properties[3].Value            
                    TimeCreated = $event.TimeCreated            
                    Result = "Success"            
                    Type = $type            
                }            
}            
            
$RDPevents | Sort-Object -Descending:$true -Property TimeCreated | Format-Table -AutoSize -Wrap

Beware of character [int]160

While working on the OperatingSystem attribute of computer objects in Active Directory, we noticed that some fields have special characters like “™” and “®”.
A few months later, when we exported these fields to a CSV ASCII file, we noticed that there were additional characters.
When we looked in the console, we couldn’t see anything. To help us see what’s going on, I’ve used the following command to convert each character to its decimal value.

(Get-ADComputer -Identity PTU730$ -Properties *).OperatingSystem -split"(?<=\G.{1})" | %  {            
    if ($_ -ne "")            
    {            
        $_ + "-> " + [int][char]$_            
    }            
}

character 160

I’ve copied the space between ‘Windows’ and ‘7’ from the edited field in the adsiedit.msc console and pasted it in the console.
Now, here is a tip to split a string using the “\s” that represents a regular expression matching any white space character instead of the simple space character.
character 160

Coloring WindowsUpdate.log

The other day I’ve seen the post about WinDump Color Highlighting PowerShell Script from Jason Fossen and thought that it could be very useful if the WindowsUpdate.log could be colored.

On February, there has been a problem with the Silverlight update that seemed to fail with error 80070643 but it was actually correctly installed.
Doug Neal from Microsoft Update explained on the patchmanagement.org distribution list that it was due to a detection logic error.

Here’s an extract how the error about Silverlight (MS12-016) is displayed using the colored extract of the WindowsUpdate.log
WU log extract

Later on, I’ve also found some explanations about How to read the Windowsupdate.log file.

I did not focus too much on performance (I’ve added a progress bar to see what’s going on) and I’ve added the ability to search either by a date or a string. Enjoy 🙂

#Requires -Version 2.0            
            
            
            
param(            
   [Parameter(ParameterSetName='Date', Mandatory=$true, Position=0)]            
    [System.DateTime]${Date},            
            
    [Parameter(ParameterSetName='DateString', Mandatory=$true, Position=0)]            
    [system.string]${DateString},            
            
    [Parameter(ParameterSetName='SearchString', Mandatory=$true, Position=0)]            
    [system.string]${SearchString}            
)            
            
            
if (Test-Path $env:systemroot\WindowsUpdate.log)            
{            
    $allcontent = Get-Content -Path $env:systemroot\WindowsUpdate.log            
} else {            
    Write-Host -ForegroundColor Red -Object ("Cannot find $env:systemroot\WindowsUpdate.log")            
    exit            
}            
            
switch ($PsCmdlet.ParameterSetName)            
{            
    Date {            
        $formatteddate = "{0:yyyy}-{1:MM}-{2:dd}" -f $Date,$Date,$Date            
    }            
    DateString {            
        if (($DateString -match "^\d{4}\-\d{2}\-\d{2}$") -or ($DateString -match "^\d{4}$") -or ($DateString -match "^\d{4}\-\d{2}"))            
        {            
            $formatteddate = $DateString            
        } else {            
            Write-Host -ForegroundColor Red -Object "The expected DateString format is yyyy-MM-dd"            
            exit            
        }            
    }            
}            
            
$parsedcontent = @()            
$count = 0            
foreach ($line in $allcontent)            
{            
    $count++            
    Write-Progress -activity "Parsing WindowsUpdate.log" -status "Percent added: " -PercentComplete (($count/$allcontent.Count)*100)            
            
    if ($formatteddate -ne $null)            
    {            
        if ($line.startswith($formatteddate))            
        {            
            # Add each line to our array            
            $parsedcontent += $line            
        }            
    }            
    if ($SearchString -ne $null)            
    {            
        if ($line -match [regex]::escape($SearchString))            
        {            
            # Add each line to our array            
            $parsedcontent += $line            
        }            
    }            
}            
            
$count = 0            
foreach ($line in $parsedcontent)            
{            
    $count++            
    Write-Progress -activity "Displaying colored WindowsUpdate.log" -status "Percent added: " -PercentComplete (($count/$parsedcontent.Count)*100)            
    # First split the line by Tabs            
    $arString = @($line -split ([char]9))            
    # Date and time            
    Write-Host -ForegroundColor Cyan -NoNewline -Object ($arString[0] + " " + $arString[1] +  " ")            
    # PID and TID            
    Write-Host -ForegroundColor Gray -NoNewline -Object ($arString[2] + " " + $arString[3] +  " ")            
    # Component            
    Write-Host -ForegroundColor Blue -NoNewline -Object ($arString[4] + " ")            
    # Text: get it as a single line            
    $restofline = -join ($arString[5..($arString.Count-1)])            
    switch ($restofline)            
    {            
        # Lines that match only a reptition of ":","*","-","+" or "#" that acts as line separators            
        {$_ -match [regex]'^[\:|\*|\-|\+|\#]+$'}        {Write-Host -ForegroundColor DarkGray -Object $_ -NoNewline}            
        # Only lines that begin by either ":","*","-","+","=" or "#"            
        {$_ -match [regex]'^[\:|\*|\-|\+|\=|\#]+\s{1}'} {Write-Host -ForegroundColor Cyan -Object $_ -NoNewline}            
        # Lines that begin by ">>-- "              
        {$_ -match [regex]'^>>\-\-\s{1}'}               {Write-Host -ForegroundColor Gray -Object $_ -NoNewline}            
        # Lines that begin by "  + "            
        {$_ -match [regex]'^\s{2,3}\+\s{1}.'}           {Write-Host -ForegroundColor DarkGray -Object $_ -NoNewline}            
        # Lines that begin by "  *       {"            
        {$_ -match [regex]'^\s{2,3}\*\s{7}\{.'}         {Write-Host -ForegroundColor DarkGray -Object $_ -NoNewline}            
        # Lines that begin by "  *   Title = "            
        {$_ -match [regex]'^\s{2,3}\*\s{3}Title\s=\s'}  {Write-Host -ForegroundColor White -Object $_ -NoNewline}            
        # Lines that begin by "  * Added update {"            
        {$_ -match [regex]'^\s{2,3}\*\s{1}Added\s{1}update\s{1}\{'} {Write-Host -ForegroundColor DarkGray -Object $_ -NoNewline}            
        default            
        {            
            # Now we split other lines and the remaining text by spaces            
            $arrestofline = @($restofline.split(([char]32)))            
            foreach ($t in $arrestofline)            
            {            
                switch($t)            
                {            
                    # Case sensitive match            
                    {$_ -cmatch "WARNING:"} {Write-Host -ForegroundColor Yellow   -Object $_ -NoNewline}            
                    {$_ -cmatch "FATAL:"}   {Write-Host -ForegroundColor Red      -Object $_ -NoNewline}            
                    # Any hexadecimal notation except 0x00000000 that may be followed by "." or ",".            
                    {$_.StartsWith("0x") -and ($_ -notmatch '^0x00000000([\.|\,]??)')}   {Write-Host -ForegroundColor Red -Object $_ -NoNewline}            
                    { ($_ -match '^0x00000000([\.|\,]??)')} {Write-Host -ForegroundColor Green    -Object $_ -NoNewline}            
                    # Match an hexadecimal number followed by "." or not.            
                    {$_ -match "^([0-9a-f]{8})((\.)??)$"}   {Write-Host -ForegroundColor Red      -Object $_ -NoNewline}            
                    # Match a number but a hexadecimal            
                    {$_ -match "^\d{1,7}(([,|\)])??)$"}     {Write-Host -ForegroundColor Magenta  -Object $_ -NoNewline}            
                    {$_ -match "^\d{9,20}(([,|\)])??)$"}    {Write-Host -ForegroundColor Magenta  -Object $_ -NoNewline}            
                    # Match a Date Time yyyy-MM-dd HH:mm:ss            
                    {$_ -match "^\d{4}\-\d{2}\-\d{2}$"}     {Write-Host -ForegroundColor White -Object $_ -NoNewline}            
                    {$_ -match "^\d{2}\:\d{2}\:\d{2}$"}     {Write-Host -ForegroundColor White -Object $_ -NoNewline}            
                    # Exact matches            
                    {$_ -eq 'state:0'} {Write-Host -ForegroundColor Green    -Object $_ -NoNewline}            
                    {$_ -eq 'hr=0x0'}  {Write-Host -ForegroundColor Green    -Object $_ -NoNewline}            
                    {$_ -eq 'True'}    {Write-Host -ForegroundColor Magenta  -Object $_ -NoNewline}            
                    {$_ -eq 'False'}   {Write-Host -ForegroundColor Magenta  -Object $_ -NoNewline}            
                    # Begins by 'Yes and might be followd by ",",";" or nothing            
                    {$_ -match '^Yes(([,|;])??)$'} {Write-Host -ForegroundColor Magenta -Object $_ -NoNewline}            
                    {$_ -match '^No(([,|;])??)$'}  {Write-Host -ForegroundColor Magenta -Object $_ -NoNewline}            
                    # Anything that matches a URL            
                    {$_.StartsWith("http://")}     {Write-Host -ForegroundColor Green   -Object $_ -NoNewline}            
                    {$_.StartsWith("https://")}    {Write-Host -ForegroundColor Green   -Object $_ -NoNewline}            
                    {$_ -eq '(00000000)'}          {Write-Host -ForegroundColor Green   -Object $_ -NoNewline}            
                    {$_ -match 'successfully'}     {Write-Host -ForegroundColor Green   -Object $_ -NoNewline}            
                    # Anything that matches the 'Succeeded' word followed by ",","]","." or nothing            
                    {$_ -match 'Succeeded(([,|\]\.])??)$'} {Write-Host -ForegroundColor Green -Object $_ -NoNewline}            
                    {$_ -match '^Failed(([,|\]\.])??)$'}   {Write-Host -ForegroundColor Red   -Object $_ -NoNewline}            
                    # A keyword that may begin by "(" or not and followed by 'hr=' and a repetition of numbers            
                    {$_ -match "^((\()??)hr=[0-9][0-9]+"}  {Write-Host -ForegroundColor Red   -Object $_ -NoNewline}            
                    # A path that contains the '\SoftwareDistribution\' keyword            
                    {$_ -match [regex]'\\SoftwareDistribution\\'} {Write-Host -ForegroundColor White -Object $_ -NoNewline}            
                    default {Write-Host -ForegroundColor DarkGray -Object $_ -NoNewline}            
                } # end if switch            
                Write-Host -ForegroundColor Yellow -Object " " -NoNewline            
            } # end of foreach            
        } # end of default            
    } # end of switch            
    # Now insert a 'new line'            
    Write-Host -Object ([char]10)            
}            

PSH3 Workflow resources

When Windows PowerShell Met Workflow by PowerShell Team (introduction)

WMF3 CTP2 Windows PowerShell Workflow.pdf (already published a few months ago)

Video: Bruce Payette – PowerShell Workflows @Frankfurt Deep Dive conference

Windows PowerShell Workflow Module documentation on technet

Writing a Windows PowerShell Workflow (MSDN documentation)

Microsoft.PowerShell.Workflow Namespace documentation on MSDN

What happened to special characters

In powershell v3 beta, the backtick still escapes characters but there aren’t any special characters anymore ?:

In V2 you have:
special characters V2
Whereas in V3 you have:
special characters v3

The technet page about special characters in powershell v2 says that:

The following special characters are recognized by Windows PowerShell:

`0 Null
`a Alert
`b Backspace
`f Form feed
`n New line
`r Carriage return
`t Horizontal tab
`v Vertical tab
These characters are case-sensitive.

In V3 we actually need to either use quotes around special characters like this

Write-Host "`n"

or we can use the corresponding ascii table characters:

`0 Null -> [char]0
`a Alert -> [char]7
`b Backspace -> [char]8
`f Form feed -> [char]12
`n New line -> [char]10
`r Carriage return -> [char]13
`t Horizontal tab -> [char]9
`v Vertical tab -> [char]11

and we can now do:

Write-Host ([char]10)            
Write-Host ("test" + [char]9 + "tab")