About Get-Service

The Get-Service cmdlet will retrieve

“objects that represent the services on a local computer or on a remote computer”.

Ambiguous way of formulating things, isn’t it? Let’s try to uncover the meaning behind.

Get-Service | Measure
Get-Service -Name * | Measure
Get-Service -Name * -include * | Measure

I always get a count of 169 services as you can see:

The Get-Service cmdlet output is a System.ServiceProcess.ServiceController .Net object.

Get-Service | Get-Member -Static

It has 2 static methods, GetServices and GetDevices.

What’s their definition?

[System.ServiceProcess.ServiceController]::GetServices
[System.ServiceProcess.ServiceController]::GetDevices


No way to authenticate to query a remote computer. That’s why Get-Service doesn’t support the Credential parameter.

[System.ServiceProcess.ServiceController]::GetServices() |
Measure

I got the same number of services as the Get-Service cmdlet.

[System.ServiceProcess.ServiceController]::GetDevices() | 
Measure


What the heck? What are these 237 additional services?

The GetDevices method

retrieves the device driver services on the local computer.

, i.e, KernelDriver and FileSystemDriver service types.

([System.ServiceProcess.ServiceController]::GetDevices()).ServiceType | 
Sort -Unique

The GetServices method

retrieves all the services on the local computer, except for the device driver services.

([System.ServiceProcess.ServiceController]::GetServices()).ServiceType | 
Sort -Unique

Let’s do some maths:

[System.ServiceProcess.ServiceController]::GetDevices() +
[System.ServiceProcess.ServiceController]::GetServices() | 
Measure

# Are they unique?
[System.ServiceProcess.ServiceController]::GetDevices() + 
[System.ServiceProcess.ServiceController]::GetServices() | 
Sort -Unique | Measure


In other words, the Get-Service cmdlet outputs by default only the objects that you can obtain using the GetServices method.
Really?

Let’s find the name of one kernel driver service.

[System.ServiceProcess.ServiceController]::GetDevices() |
Select -Last 1 -Property Status,Name,ServiceType,DisplayName |
Format-Table -AutoSize

If I start type the first two letters of the service name I’m looking for and rely on the tab completion, only services that you can get with the GetServices method are enumerated.

If I type:

Get-Service -Name Wud*
Get-Service -Name WudfP*
Get-Service -Name WudfPf

I get:

Based on the above results, the Get-Service cmdlet is able to retrieve kernel driver or file system driver services only if you know their exact name. Wildcard are not allowed in this case.

Why Kernel drivers and file system drivers aren’t returned by the Get-Service cmdlet by default? I can probably guess the answer.
The Get-Service is designed to pass objects through the pipeline to other cmdlets that take actions like “Start”,”Stop”,”Pause”,”Resume”.
Let’s say I do:

Get-Service -Name wu* | 
Stop-Service -PassThru -Verbose | 
Set-Service -StartupType Disabled -Verbose

Better safe than sorry. Imagine there’s a critical kernel driver returned by the above wildcard query. It would probably immediately end with a blue screen.
If you really know what you do, you have to know the exact name of kernel driver and file system driver services to be able to manipulate them with the built-in *-Service cmdlets.

Bitlocker Basic Deployment

I had to work recently on Bitlocker to encrypt the Operating System volume both on Windows 7 and 8.1 computers.
We choose a basic deployment scenario and decide to stick to the following best practice that you can find in the BitLocker Frequently Asked Questions (FAQ)

What is the best practice for using BitLocker on an operating system drive?

The recommended practice for BitLocker configuration on an operating system drive is to implement BitLocker on a computer with a TPM version 1.2 or 2.0 and a Trusted Computing Group (TCG)-compliant BIOS or UEFI firmware implementation, plus a PIN. By requiring a PIN that was set by the user in addition to the TPM validation, a malicious user that has physical access to the computer cannot simply start the computer.

  • Prerequisites:
    • Have a laptop or tablet equiped with a TPM
    • Check Active Directory requirements in the FAQ
    • Does BitLocker require a schema extension to store recovery information in AD DS?
      Windows Server 2008, Windows Server 2008 R2, and Windows Server 2012 For these servers the schema already includes the required attributes.

  • Step 1: the group policy
  • The first step was to create a group policy that would cover our needs. One of the key point is the recovery process and we wanted to make sure no machine gets (bit)locked before its recovery key is stored in Active Directory. By the way, here is the BitLocker Recovery Guide

    That group policy is linked to an OU where we have moved our Windows 7 and Windows 8.1 computers.

  • Step 2: Enable the TPM
    • …using the built-in manage-bde.exe on Windows 7
    • manage-bde.exe -tpm -TurnOn
      
    • …using built-in cmdlets on Windows 8.1
    • Initialize-Tpm -AllowPhysicalPresence -AllowClear
      

    • …using WMI
    • $tpm = (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftTPM -Class Win32_TPM)
      $tpm.SetPhysicalPresenceRequest(10)
      

    After this step, you need to restart the computer and press a key to confirm that you want to enable and activate the TPM.
    On Surface 1, I had to press Fn+F12 and a the HP laptop I had to press F1.

  • Step 3: Own the TPM
    • …using the built-in manage-bde.exe on Windows 7
    • manage-bde.exe -tpm -TakeOwnership MyPassPhrase
      
    • …using built-in cmdlets on Windows 8.1
    • $HT =@{
      OwnerAuthorization = (Get-Tpm).OwnerAuth ;
      NewOwnerAuthorization = (ConvertTo-TpmOwnerAuth -PassPhrase "MyPassPhrase") ;
      }
      Set-TpmOwnerAuth @HT
      

    • …using WMI
    • $tpm = (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftTPM -Class Win32_TPM)
      $ownerauth = $tpm.ConvertToOwnerAuth("MyPassPhrase").OwnerAuth
      $tpm.TakeOwnership($ownerauth)
      

  • Step 4: Add a PIN
    • …using the built-in manage-bde.exe on Windows 7
    • manage-bde.exe -protectors -add C: -tpmandpin 12345678
      
    • …using built-in cmdlets on Windows 8.1
    • $SecureString = ConvertTo-SecureString "12345678" -AsPlainText -Force
      Add-BitLockerKeyProtector -MountPoint "C:" -Pin $SecureString -TPMandPinProtector
      

    • …using WMI
    • (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').ProtectKeyWithTPMAndPIN($null,$null,"12345678")
      

  • Step 5: Add a recovery password
    • …using the built-in manage-bde.exe on Windows 7
    • manage-bde -protectors -add C: -RecoveryPassword
      
    • …using built-in cmdlets on Windows 8.1
    • Add-BitLockerKeyProtector -MountPoint C: -RecoveryPasswordProtector
      
    • …using WMI
    • (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').ProtectKeyWithNumericalPassword()
      

  • Step 6: Backup the recovery password into Active Directory
    • …using the built-in manage-bde.exe on Windows 7
    • for /f "tokens=1-2 delims=: " %i in ('manage-bde -protectors -get C: -Type recoverypassword ^| findstr /i /c:"ID: "') do @ set _ID=%j
      manage-bde.exe -protectors -adbackup c: -ID "%_ID%"
      

    • …using built-in cmdlets on Windows 8.1
    • Backup-BitLockerKeyProtector -MountPoint "C:" -KeyProtectorId (
      (Get-BitLockerVolume -MountPoint "C:").KeyProtector | 
      Where KeyProtectorType -eq RecoveryPassword
      ).KeyProtectorId
      
    • …using WMI
    • 
      # Either create the random 48-digit recovery password
      $res = (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').ProtectKeyWithNumericalPassword()
      #  and get the volume protector ID returned
      $VolumeKeyProtectorID = $res.VolumeKeyProtectorID
      
      # ...or retrieve it afterwards like this
      $VolumeKeyProtectorID = (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').GetKeyProtectors(3) | Select -Expand VolumeKeyProtectorID 
      
      # Backup
      (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').BackupRecoveryInformationToActiveDirectory($VolumeKeyProtectorID)
      

  • Step 7: Encrypt the volume
    • …using the built-in manage-bde.exe on Windows 7
    • manage-bde.exe -on C: -SkipHardwareTest
      

    • …using built-in cmdlets on Windows 8.1
    • Enable-BitLocker -MountPoint C: -EncryptionMethod Aes128 -RecoveryPasswordProtector -SkipHardwareTest
      
    • …using WMI
    • (Get-WmiObject -Namespace root/cimv2/Security/MicrosoftVolumeEncryption -Class Win32_EncryptableVolume -Filter 'DriveLetter = "C:"').Encrypt(1)
      

    Bonus: Get the recovery password stored in AD

    Get-ADObject -SearchBase (Get-ADComputer MyComputerName).DistinguishedName -Filter 'ObjectClass -eq "msFVE-RecoveryInformation"' -Properties Name,msFVE-RecoveryPassword | 
    ForEach-Object {
    
        $reco = @(([regex]'(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}\:\d{2})(?<GUID>\{[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-){4}[A-Za-z0-9]{12}\})').Matches($_.Name) | Select -Expand Groups | Select -Last 2)
        
        New-Object -TypeName PSObject -Property @{
            Date = (Get-Date -Date $reco[0].Value)
            PasswordID = $reco[1].Value
            RecoveryKey = $_.'msFVE-RecoveryPassword'
        }
    
    } | Sort -Property Date -Descending:$false | 
    Select -Last 1 -ExpandProperty RecoveryKey
    

    NB: Domain Admin credentials are required to read this info by default.

    Mount and dismount volume shadow copies

    Volume shadow copies can help you to:

    All the demos, I’ve seen so far were using the built-in DOS mklink command to mount a volume shadow copy and vssadmin to list shadow copies. While playing with vssadmin, I’ve found a use of case of the context parameter of the Select-String cmdlet.

    vssadmin list shadows | 
    Select-String -Pattern "shadow copies at creation time" -Context 0,3 |
    ForEach-Object {
        [pscustomobject]@{
            Path = (($_.Context.PostContext -split "\r\n")[2] -split ':')[1].Trim();
            InstallDate = ($_.Line -split ':\s',2)[1];
        }
    }
    


    Ugly, I know. Let’s forget about this, there’s a very simple way return the list volume shadow copies on Windows as objects.

    Last year, Boe Prox wrote an excellent article where he showed how to Create a Symbolic Link using PowerShell. Kudos to him, I’ll reuse his brilliant code :-D

    Unfortunately, he didn’t show how to remove these symbolic links. The following MSDN article tells us how:

    To remove a symbolic link, delete the file (using DeleteFile or similar APIs) or remove the directory (using RemoveDirectory or similar APIs) depending on what type of symbolic link is used.

    Now let’s see what I propose to mount and dismount volume shadow copies:

    Function Mount-VolumeShadowCopy {
    <#
        .SYNOPSIS
            Mount a volume shadow copy.
        
        .DESCRIPTION
            Mount a volume shadow copy.
         
        .PARAMETER ShadowPath
            Path of volume shadow copies submitted as an array of strings
         
        .PARAMETER Destination
            Target folder that will contain mounted volume shadow copies
                 
        .EXAMPLE
            Get-CimInstance -ClassName Win32_ShadowCopy | 
            Mount-VolumeShadowCopy -Destination C:\VSS -Verbose
    
    #>
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [ValidatePattern('\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy\d{1,}')]
        [Alias("DeviceObject")]
        [String[]]$ShadowPath,
    
        [Parameter(Mandatory)]
        [ValidateScript({
            Test-Path -Path $_ -PathType Container
        }
        )]
        [String]$Destination
    )
    Begin {
        Try {
            $null = [mklink.symlink]
        } Catch {
            Add-Type @"
            using System;
            using System.Runtime.InteropServices;
     
            namespace mklink
            {
                public class symlink
                {
                    [DllImport("kernel32.dll")]
                    public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
                }
            }
    "@
        }
    }
    Process {
    
        $ShadowPath | ForEach-Object -Process {
    
            if ($($_).EndsWith("\")) {
                $sPath = $_
            } else {
                $sPath = "$($_)\"
            }
           
            $tPath = Join-Path -Path $Destination -ChildPath (
            '{0}-{1}' -f (Split-Path -Path $sPath -Leaf),[GUID]::NewGuid().Guid
            )
            
            try {
                if (
                    [mklink.symlink]::CreateSymbolicLink($tPath,$sPath,1)
                ) {
                    Write-Verbose -Message "Successfully mounted $sPath to $tPath"
                } else  {
                    Write-Warning -Message "Failed to mount $sPath"
                }
            } catch {
                Write-Warning -Message "Failed to mount $sPath because $($_.Exception.Message)"
            }
        }
    
    }
    End {}
    }
    
    Function Dismount-VolumeShadowCopy {
    <#
        .SYNOPSIS
            Dismount a volume shadow copy.
        
        .DESCRIPTION
            Dismount a volume shadow copy.
         
        .PARAMETER Path
            Path of volume shadow copies mount points submitted as an array of strings
         
        .EXAMPLE
            Get-ChildItem -Path C:\VSS | Dismount-VolumeShadowCopy -Verbose
            
    
    #>
    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [Alias("FullName")]
        [string[]]$Path
    )
    Begin {
    }
    Process {
        $Path | ForEach-Object -Process {
            $sPath =  $_
            if (Test-Path -Path $sPath -PathType Container) {
                if ((Get-Item -Path $sPath).Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
                    try {
                        [System.IO.Directory]::Delete($sPath,$false) | Out-Null
                        Write-Verbose -Message "Successfully dismounted $sPath"
                    } catch {
                        Write-Warning -Message "Failed to dismount $sPath because $($_.Exception.Message)"
                    }
                } else {
                    Write-Warning -Message "The path $sPath isn't a reparsepoint"
                }
            } else {
                Write-Warning -Message "The path $sPath isn't a directory"
            }
         }
    }
    End {}
    }
    

    Let’s see these two functions in action:

    • Mount all volume shadow copies
    • Get-CimInstance -ClassName Win32_ShadowCopy | 
      Mount-VolumeShadowCopy -Destination C:\VSS -Verbose
      

    • Dismount all volume shadow copies mount points located in C:\VSS
    • Get-ChildItem -Path C:\VSS | 
      Dismount-VolumeShadowCopy -Verbose
      

    PowerShell rocks! No doubt 8-)

    Enable or Disable Event Logs

    I find the ability of the Get-WinEvent cmdlet to read event logs absolutely awesome. But I also find the lack of other commands that match the WinEvent noun disturbing ;-)

    Get-Command -Noun WinEvent
    

    To fix this, I propose to add two functions to your toolbox to either enable or disable eventlogs.
    I’ve made the code compatible with version 2.0 so that you can use the code everywhere.
    But there are also some limitations. The functions are designed to be used locally. I mean that that they don’t support natively credentials and remoting like the original Get-WinEvent cmdlet does.
    Note also that you must have admin privileges to enable or disable a log.

    #Requires -version 2.0
    
    Function Enable-WinEvent  {
    <#
        .SYNOPSIS
            Enable event logs by name
       
        .DESCRIPTION
            Enable event logs by their exact full name
        
        .PARAMETER PassThru
            Switch to allow System.Diagnostics.Eventing.Reader.EventLogConfiguration objects to flow into the pipeline
        
        .PARAMETER LogName
            Array of eventlog names
        
        .EXAMPLE
            Enable-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL","Microsoft-Windows-AppLocker/MSI and Script" -PassThru
        
        .EXAMPLE
            Get-WinEvent -ListLog * | Where {$_.LogName -Match "Applocker" } | Enable-WinEvent -Verbose
    
        .EXAMPLE
            Get-WinEvent -ListLog * | 
            Where {$_.LogName -Match "Applocker" } | 
            Enable-WinEvent -Passthru | 
            Format-Table -Property LogMode,MaximumSizeInBytes,RecordCount,LogName,isEnabled -AutoSize
    
        .NOTES
            You cannot enable or disable a classic log. A warning will be issued if you do.
    #>
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias('Log')]
        [system.string[]]$LogName,
        [Switch]$Passthru
    )
    Begin{
        $allLogs = Get-WinEvent -ListLog * -Force -ErrorAction SilentlyContinue
    }
    Process {
        $LogName | ForEach-Object -Process {
            $log = $wineventlog = $null
            $log = $_
            # Get rich object that maps the logname
            $wineventlog = $allLogs | Where { $_.LogName -EQ $log}
            
            if ($wineventlog) {
                if ($wineventlog.isClassicLog) {
                    Write-Warning "Cannot enable classic log: $_"
                } else {
                    try {
                        $wineventlog.set_IsEnabled($true) | Out-Null
                        $wineventlog.SaveChanges()
                        Write-Verbose -Message "Successfully enabled log $($wineventlog.logname)"    
                        if ($Passthru) { $wineventlog }
                    } catch {
                        Write-Warning -Message "Failed to enable $($wineventlog.logname) because $($_.Exception.Message)"
                    }        
                }
            } else {
                Write-Warning "Cannot find a log named $_"
            }
        }
    }
    End {}
    }
    
    Function Disable-WinEvent  {
    <#
        .SYNOPSIS
            Disable event logs by name
       
        .DESCRIPTION
            Disable event logs by their exact full name
        
        .PARAMETER PassThru
            Switch to allow System.Diagnostics.Eventing.Reader.EventLogConfiguration objects to flow into the pipeline
        
        .PARAMETER LogName
            Array of eventlog names
        
        .EXAMPLE
            Disable-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL","Microsoft-Windows-AppLocker/MSI and Script" -PassThru
        
        .EXAMPLE
            Get-WinEvent -ListLog * | Where {$_.LogName -Match "Applocker" } | Disable-WinEvent -Verbose
    
        .EXAMPLE
            Get-WinEvent -ListLog * | 
            Where {$_.LogName -Match "Applocker" } | 
            Disable-WinEvent -Passthru | 
            Format-Table -Property LogMode,MaximumSizeInBytes,RecordCount,LogName,isEnabled -AutoSize
    
        .NOTES
            You cannot enable or disable a classic log. A warning will be issued if you do.
    #>
    
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
        [Alias('Log')]
        [system.string[]]$LogName,
        [Switch]$Passthru
    )
    Begin{
        $allLogs = Get-WinEvent -ListLog * -Force -ErrorAction SilentlyContinue
    }
    Process {
        $LogName | ForEach-Object -Process {
            $log = $wineventlog = $null
            $log = $_
            # Get rich object that maps the logname
            $wineventlog = $allLogs | Where { $_.LogName -EQ $log}
            
            if ($wineventlog) {
                if ($wineventlog.isClassicLog) {
                    Write-Warning "Cannot disable classic log: $_"
                } else {
                    try {
                        $wineventlog.set_IsEnabled($false) | Out-Null
                        $wineventlog.SaveChanges()
                        Write-Verbose -Message "Successfully disabled log $($wineventlog.logname)"
                        if ($Passthru) { $wineventlog }
                    } catch {
                        Write-Warning -Message "Failed to enable $($wineventlog.logname) because $($_.Exception.Message)"
                    }        
                }
            } else {
                Write-Warning "Cannot find a log named $log"
            }
        }
    }
    End {}
    }
    

    Let’s see the functions in action.

        Disable two Applocker logs
      Disable-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL","Microsoft-Windows-AppLocker/MSI and Script" -PassThru
      

        Display the Applocker logs and see which ones are enabled
      Get-WinEvent -ListLog * | 
      Where LogName -Match "Applocker" | 
      Format-Table -Property LogMode,MaximumSizeInBytes,RecordCount,LogName,isEnabled -AutoSize
      

        Let’s enable all applocker logs and use the passthru switch to pass directly the log to Format-Table cmdlet to report their status
      Get-WinEvent -ListLog * | 
      Where {$_.LogName -Match "Applocker" } | 
      Enable-WinEvent -Passthru | 
      Format-Table -Property LogMode,MaximumSizeInBytes,RecordCount,LogName,isEnabled -AutoSize
      

    That’s all folks! May the force be with you :-D

    Engine health report

    Yesterday, we had strange reports from our custom security compliance audit process based on PowerShell and remoting.

    We’ve got a computer that said:
    The following error occurred while loading the extended type data file: Microsoft.PowerShell.Core, C:\Windows\System32\WindowsPowerShell\v1.0\types.ps1xml : Exception: ‘♦’, hexadecimal value 0×04, is an invalid character. Line 212, position 4.
    What a nice and precise error that tells you immediately what’s wrong.

    As you can see in the following screenshot, I got the above error as soon as I enter the remote session and the automatic variable $PSVersionTable being altered and reporting a hashtable with empty keys names:

    What’s wrong with types.ps1.xml? It’s a signed protected system file. I mean that it has a signature block at the end of the file:

    ..and that it’s protected by NTFS ACLs:

    Ok, let’s compare a safe computer named Pxxxx5 and the failing one Pxxxx7.

    If we check the file size, timestamp,…

    … there’s nothing wrong.

    But if I check its signature…

    … the built-in Get-AuthenticodeSignature cmdlet shows that the file on the failing computer has lost its integrity.

    If I compare the content, we can see:

    Weird, isn’t it?

    Besides the fact the error reported tells you exactly what’s wrong and that PowerShell lets you easily analyze the problem, there’s another good news:
    The following event is logged in the “Windows PowerShell” log.
    . This makes the issues easily detectable.

    Get-WinEvent -ComputerName $computer -FilterHashtable @{ 
        LogName = "Windows PowerShell" ;
        Level = 3 ;
        Id = 103 
    }  -MaxEvents 1
    

    You may wonder how the file was corrupted.
    We’ve talk to the user about the behavior of his computer. He said that there’s was a power cut in the morning.

    Understanding Format cmdlets view parameter

    Sometimes the quickest way to discover something new in PowerShell is by making an typo error.

    I wanted to know how many different views existed for the Get-Process cmdlet.

    Although there are probably many ways to find the answer, the quickest one is by trying to guess it.

    Get-Process | Format-Table -View GuessOrTypo
    


    As you can see in the above screenshot, I got my answer straight away in the error message.
    Yes, you should read carefully errors messages, they are most of the time useful and can even contain the answer on how to fix your typo.

    If you’ve a problem with the color of error messages. You can change it to a different color

    #requires -version 3.0
     if ($ise) {
        $Host.PrivateData.ErrorForegroundColor = [System.Windows.Media.Color]'Green'
     } else {
        $Host.PrivateData.ErrorForegroundColor = [system.ConsoleColor]::Green
     }
    

    Let’s see what these views look like:

    Get-Process -Id $PID
    Get-Process -Id $PID | Format-Table -View process
    Get-Process -Id $PID | Format-Table -View Priority
    Get-Process -Id $PID | Format-Table -View StartTime
    

    Another way of listing views that apply to the System.Diagnostics.Process object consists in digging into its formatting XML definition file

    (
      [xml](Get-Content "$PSHOME\DotNetTypes.format.ps1xml")
    ).SelectNodes(
    '//ViewSelectedBy[TypeName = "System.Diagnostics.Process"]/..'
    )
    

    The 4th view that you can see in the above snapshot that isn’t a Table is actually associated to the Format-Wide cmdlet

    Get-Process | Format-Wide -View process
    

    If you want to understand how PowerShell formats objects to display them, you can read more on this subject on these pages:

    …and last but not least in the built-in help of PowerShell thanks to this shortcut:

    Get-Help about_format.ps1xml
    

    Testing MS13-098 Certificate Padding Check

    Microsoft published last year both a security advisory and a security bulletin about Windows Authenticode Signature Verification.

    The security bulletin was revised on May 21, 2014 to announce that the deadline of June 10 was postponed to August 12, 2014.
    Two deadlines on June 10 breaking Windows was a bit confusing for customers and support teams, so the Marketing department changed the deadline… I’m kidding, of course ;-)

    I reported the above new cut-off date to the pachmanagement.org mailing list and some questions were raised about how to activate the certificate padding check and what tools should be used to test.
    I replied that:

    Now, I’d like share my experience, develop and illustrate what I wrote in the above message.

  • Step 1: Check that you’ve MS13-098 installed
  • My test computer is Windows 7 x64 machine with PowerShell 3.0 installed.

    Get-HotFix -Id "KB2893294"
    
  • Step 2: Test without having certficate padding check enabled
  • Prerequisites: Like Didier Stevens, I’ve two “Chrome installers” that I renamed.

    # Download the latest version of sigcheck
    Invoke-WebRequest -Uri http://live.sysinternals.com/sigcheck.exe -OutFile .\sigcheck.exe
    Unblock-File .\sigcheck.exe
    # Download the AnalyzePESig tool and extract it manually
    Invoke-WebRequest -Uri https://didierstevens.com/files/software/AnalyzePESig_V0_0_0_3.zip -OutFile .\AnalyzePESig_V0_0_0_3.zip
    


    Note that the Get-AuthenticodeSignature reports both portable executable (PE) installers as signed and the trust verifying events are logged into the Certificate API v2 log.

    Idem for sigcheck from Mark Russinovich

    I’ve created locally an Applocker policy with default rules and added a deny rule on the certificate found in the installer. Note that when I test the file against the applocker execution rules, it reports that its execution would be denied based on the publisher rule I introduced.

  • Step 3: Enabled the certificate padding check
  • This step is documented in the security advisory
    Here’s how I’ve enabled it with PowerShell based on the information found in the above advisory:

    if (-not(Test-Path -Path "HKLM:\Software\Microsoft\Cryptography\Wintrust\Config" -PathType Container)) {
        try {
            New-Item -Path HKLM:\Software\Microsoft\Cryptography\Wintrust\Config -ItemType Container -Force -ErrorAction Stop
            New-ItemProperty -Path HKLM:\Software\Microsoft\Cryptography\Wintrust\Config -Name EnableCertPaddingCheck -Value 1 -PropertyType String -ErrorAction Stop
            if ([System.Environment]::Is64BitOperatingSystem) {
                if (-not(Test-Path -Path "HKLM:\Software\Wow6432Node\Microsoft\Cryptography\Wintrust\Config" -PathType Container)) {
                    New-Item -Path HKLM:\Software\Wow6432Node\Microsoft\Cryptography\Wintrust\Config -ItemType Container -Force -ErrorAction Stop
                    New-ItemProperty -Path HKLM:\Software\Wow6432Node\Microsoft\Cryptography\Wintrust\Config -Name EnableCertPaddingCheck -Value 1 -PropertyType String -ErrorAction Stop
                }
            }
        } catch {
            Write-Warning -Message "Failed to create registry key because $($_.Exception.Message)"
        }
    }
    Get-Service -Name CryptSvc -Verbose | Stop-Service -Verbose -Force -PassThru | Start-Service -PassThru -Verbose
    

    A computer restart isn’t actually needed, you can only restart the Cryptographic Services. The Applocker service will be also restarted as it depends on the cryptographic services.

  • Step 4: Enable additional verbose logging in the CAPI2 event log
  • # Enable verbose diagnostic logging.
     
    # Add a DWORD (32-bit) value DiagLevel with value of 0x00000005
    Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\Crypt32' -Name "DiagLevel" -Type DWORD -Value 5
     
    # Add a QWORD (64-bit) value DiagMatchAnyMask with value of 0x00ffffff
    Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\Crypt32' -Name "DiagMatchAnyMask" -Type QWORD -Value 0x00ffffff
     
    # Enable CAPI2 operational logging
     
    $log = New-Object -TypeName System.Diagnostics.Eventing.Reader.EventLogConfiguration -ArgumentList "Microsoft-Windows-CAPI2/Operational"
    $log.isEnabled = $true
    try {
         $log.SaveChanges()
    } catch {
        Write-Warning -Message "Failed to save changes because $($_.Exception.Message)"
    } 
    
    Get-Service -Name CryptSvc -Verbose | Stop-Service -Verbose -Force -PassThru | Start-Service -PassThru -Verbose
    
  • Step 5: Test the Authenticode Signature Verification with certificate padding check enabled
  • Here’s a screenshot of the installer I’ve that has the certificate issue:

    Here’s the difference with the one that hasn’t the certificate padding issue:

    Now when I use the Get-AuthenticodeSignature with verbose CAPI2 logging enabled, verify trust events are reported both for either a failure or a success

    Get-AuthenticodeSignature C:\Chrome.CertPaddingIssue.exe
    Get-AuthenticodeSignature C:\Chrome.Signed.exe
    

    Idem for sigcheck.

    To measure the impact on the local applocker policy, I do:

    Get-AppLockerPolicy -Local | 
    Test-AppLockerPolicy -Path C:\Chrome.Signed.exe
    Get-AppLockerPolicy -Local | 
    Test-AppLockerPolicy -Path C:\Chrome.CertPaddingIssue.exe
    

    … and get the following result.

    In other words, the explicit deny rule based on certificate won’t prevent the C:\Chrome.CertPaddingIssue.exe to be executed anymore. Only the implicit rule deny for everyone will block it because it’s stored in a location that isn’t listed in an explicit allow or deny rule.

  • Step 6: Report
  • $HT = @{ ErrorAction = "SilentlyContinue" }
    $AMHT = @{ Type = "NoteProperty" ; PassThru = $true ; Force = $true }
    $FilterHT = @{ FilterHashTable = @{ LogName = "Microsoft-Windows-CAPI2/Operational" ; Id = 81 }}
    Get-WinEvent @FilterHT @HT | ForEach-Object -Process {
        $xml = ([xml]($_.toXML()))
        $_ | Add-Member -Name ProcessName   -Value ($xml.Event.UserData.WinVerifyTrust.EventAuxInfo.ProcessName) @AMHT |
             Add-Member -Name Result        -Value ($xml.Event.UserData.WinVerifyTrust.Result.value) @AMHT |
             Add-Member -Name FilePath      -Value ($xml.Event.UserData.WinVerifyTrust.FileInfo.FilePath) -Force -MemberType NoteProperty
             $msg = 'The WinVerifyTrust check performed by process {0} on file {1} ended with result {2}' -f $_.ProcessName,$_.FilePath,$_.Result
             $_ | Add-Member -Name Message -Value $msg @AMHT
    }
    

    Note that when the certificate padding check is enabled, the WinVerifyTrust function returns a 0x800B0100 value, which means “No signature was present in the subject”.