RemoteActions.psm1

Function New-RemoteRDPCertificate()     # Kind of redudant; I know... (¬、¬)
{
    <#
        .SYNOPSIS
            To be used instead of the standard 'Set-RDPCertificate' when you want to create a new
            self-signed certificate on the remote computer.
    #>

    [CmdletBinding(DefaultParameterSetName="ByComputerName", PositionalBinding=$false)]
    [OutputType([psobject])]
    param
    (
        [parameter(Mandatory=$true, ParameterSetName="ByComputerName", Position=0)]
        [string] $ComputerName,

        [parameter(Mandatory=$true, ParameterSetName="ByPSSession", ValueFromPipeline=$true)]
        [System.Management.Automation.Runspaces.PSSession]
        $PSSession,

        [parameter(Mandatory=$false)]
        [ValidateNotNull()]
        [datetime] $ValidUntil = [datetime]::Now.AddYears(1),

        [parameter(Mandatory=$false)]
        [ValidateNotNull()]
        [MG.RDP.Certificates.Algorithm]
        $HashAlgorithm = [MG.RDP.Certificates.Algorithm]::SHA256,

        [parameter(Mandatory=$false)]
        [ValidateSet('2048','4096','8192','16384')]
        [int] $KeyLength = 2048,

        [parameter(Mandatory=$false)]
        [switch] $PassThru
    )
    BEGIN
    {
        $newCert = New-Object MG.RDP.Certificates.NewCertificate;

        if ($PSBoundParameters["ComputerName"])
        {
            $PSSession = New-PSSession -ComputerName $ComputerName
        }
    }
    PROCESS
    {
        $sessionArgs = @($ValidUntil, $HashAlgorithm.ToString(), $KeyLength)

        $result = Invoke-Command -Session $PSSession -HideComputerName -ArgumentList $sessionArgs -ScriptBlock {
            param
            (
                [datetime] $validUntil = $args[0],
                [string] $algorithm = $args[1],
                [int] $KeyLength = $args[2]
            )
            Add-Type -AssemblyName System.Security;
            $extsToAdd = New-Object 'System.Collections.Generic.List[object]';

            # Enhanced Key Usage
            $ekuOids = New-Object -com 'X509Enrollment.CObjectIds.1';
            $serverAuthOid = New-Object -com 'X509Enrollment.CObjectId.1';
            $eu = [System.Security.Cryptography.Oid]::FromFriendlyName("Server Authentication", [System.Security.Cryptography.OidGroup]::EnhancedKeyUsage);
            $serverAuthOid.InitializeFromValue($eu.Value);
            $ekuOids.Add($serverAuthOid);
            $ekuExt = New-Object -com 'X509Enrollment.CX509ExtensionEnhancedKeyUsage.1';
            $ekuExt.InitializeEncode($ekuOids);
            $extsToAdd.Add($ekuExt);

            # Key Usage
            $ku = New-Object -com 'X509Enrollment.CX509ExtensionKeyUsage.1';
            $ku.InitializeEncode(48);
            $ku.Critical = $false;
            $extsToAdd.Add($ku);

            # Basic Constraints
            $bc = New-Object -com 'X509Enrollment.CX509ExtensionBasicConstraints.1';
            $bc.InitializeEncode($false, -1);
            $bc.Critical = $true;
            $extsToAdd.Add($bc);

            # Private Key
            $key = New-Object -com 'X509Enrollment.CX509PrivateKey.1';
            $algId = New-Object -com 'X509Enrollment.CObjectId.1';
            $algVal = [System.Security.Cryptography.Oid]::FromFriendlyName("RSA", [System.Security.Cryptography.OidGroup]::PublicKeyAlgorithm);
            $algId.InitializeFromValue($algVal.Value);
            $key.ProviderName = 'Microsoft RSA SChannel Cryptographic Provider';
            $key.Algorithm = $algId;
            $key.KeySpec = 1;
            $key.Length = $KeyLength;
            $key.SecurityDescriptor = 'D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)';
            $key.MachineContext = 1;
            $key.ExportPolicy = 0;
            $key.Create();

            # Subject Name
            $name = New-Object -com 'X509Enrollment.CX500DistinguishedName.1';
            $name.Encode("CN=$($env:COMPUTERNAME)", 0);

            # Certificate Request
            $cert = New-Object -Com 'X509Enrollment.CX509CertificateRequestCertificate.1';
            $cert.InitializeFromPrivateKey(2, $key, [string]::Empty);
            $cert.Subject = $name;
            $cert.Issuer = $cert.Subject;
            $cert.NotBefore = [datetime]::Now;
            $cert.NotAfter = $validUntil;
            for ($i = 0; $i -lt $extsToAdd.Count; $i++)
            {
                $ext = $extsToAdd[$i];
                $cert.X509Extensions.Add($ext);
            }
            $sigId = New-Object -com 'X509Enrollment.CObjectId.1';
            $hash = [System.Security.Cryptography.Oid]::FromFriendlyName($algorithm, [System.Security.Cryptography.OidGroup]::HashAlgorithm);
            $sigId.InitializeFromValue($hash.Value);
            $cert.SignatureInformation.HashAlgorithm = $sigId;
            $cert.Encode();

            # Complete the Request to Create!
            $enroll = New-Object -com 'X509Enrollment.CX509Enrollment.1';
            $enroll.CertificateFriendlyName = "$env:COMPUTERNAME RDP";
            $enroll.InitializeFromRequest($cert);

            $endCert = $enroll.CreateRequest(1);
            $enroll.InstallResponse(2, $endCert, 1, [string]::Empty);

            [byte[]]$certBytes = [System.Convert]::FromBase64String($endCert);

            # Now use it as the RDP certificate
            $rdpCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certBytes);
            $inst = Get-CimInstance -Namespace 'root\cimv2\TerminalServices' -ClassName "Win32_TSGeneralSetting" -Filter 'TerminalName = "RDP-Tcp"';
            $inst | Set-CimInstance -Property @{ SSLCertificateSHA1Hash = $rdpCert.Thumbprint };

            return $(New-Object PSObject -Property @{
                NewCertificate = $rdpCert
            });
        }

        if ($PassThru)
        {
            Write-Output $result -NoEnumerate;
        }
    }
}