bat/PSRemoting/Enable-Remoting.ps1

<#
The MIT License (MIT)
 
Copyright (c) 2015 Objectivity Bespoke Software Specialists
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#>


function Enable-Remoting {

    <#
    .SYNOPSIS
    Configures PSRemoting on local computer.
 
    .DESCRIPTION
    Based on script for Ansible written by Trond Hindenes <trond@hindenes.com> (Version 1.0 - July 6th, 2014)
    https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1
     
    .PARAMETER AuthTypes
    Types of authentication to test (note CredSSP here is for server role).
    - Default - Negotiate (Kerberos/NTLM)
    - Basic - authentication using local user (domain credentials will not work)
    - CredSSP - allows to delegate domain credentials without Kerberos (useful for double hop).
 
    .PARAMETER Protocols
    Protocols to configure.
 
    .PARAMETER CredSSPClientDelegateComputer
    If not empty, CredSSP client role will be enabled with DelegateComputer set to value of this parameter.
 
    .PARAMETER CertSelfSigned
    If true and HTTPS is configured, a self-signed certificate will be created. Note it's validity is 365 days.
 
    .PARAMETER CertThumbprint
    Thumbprint of certificate to import (if AuthTypes contains HTTPS and CertSelfSigned is false).
 
    .PARAMETER CertSubjectName
    Subject name for the certificate (if AuthTypes contains HTTPS). It must be the same string as the one used to connect to this server.
    For example if you're going to run Invoke-Command -ComputerName 192.168.1.50, subject name must be 192.168.1.50.
 
    .EXAMPLE
    Enable-Remoting
 
    Configures CredSSP/Default on HTTP and HTTPS protocols using self-signed certificate with subject name = $env:COMPUTERNAME.
 
    .EXAMPLE
    Enable-Remoting -CredSSPClientDelegateComputer '*'
 
    Configures CredSSP/Default on HTTP and HTTPS protocols using self-signed certificate with subject name = $env:COMPUTERNAME
    and additionally configures CredSSP client role to be able to forward credentials to any computer.
 
    .EXAMPLE
    Enable-Remoting -CertSubjectName '192.168.1.200'
     
    Configures CredSSP/Default on HTTP and HTTPS protocols using self-signed certificate with subject name = 192.168.1.200.
 
    .EXAMPLE
    Enable-Remoting -AuthTypes 'Default' -Protocols 'HTTPS' -CertSelfSigned:$false -CertThumbprint $thumbprint
 
    Configures Default on HTTPS using provided certificate thumbprint.
    #>


    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory=$false)]
        [string[]] 
        [ValidateSet('Basic', 'CredSSP', 'Default')]
        $AuthTypes = @('Default'),
        
        [Parameter(Mandatory=$false)]
        [string[]] 
        [ValidateSet('HTTP', 'HTTPS')]
        $Protocols = @('HTTP', 'HTTPS'),

        [Parameter(Mandatory=$false)]
        [string[]] 
        $CredSSPClientDelegateComputer,

        [Parameter(Mandatory=$false)]
        [switch] 
        $CertSelfSigned = $true,

        [Parameter(Mandatory=$false)]
        [string] 
        $CertThumbprint,

        [Parameter(Mandatory=$false)]
        [string]
        $CertSubjectName = $env:COMPUTERNAME        
    )

    $ErrorActionPreference = "Stop"

    if ($CertSelfSigned -and $CertThumbprint) {
        Write-Error 'Both CertSelfSigned and CertThumbprint cannot be specified. If you have a certificate, please pass -CertSelfSigned:$false.'
    }
    if (!$CertSelfSigned -and !$CertThumbprint) {
        Write-Error 'CertSelfSigned must be $true if CertThumbprint is empty. If you have a certificate, please pass -CertSelfSigned:$false and valid CertThumbprint. Otherwise, please pass CertSelfSigned:$true.'
    }

    # Detect PowerShell version
    if ($PSVersionTable.PSVersion.Major -lt 3) {
        Write-Error "PowerShell/Windows Management Framework needs to be updated to 3 or higher. Stopping script"
    }

    if (!(Get-PSSessionConfiguration -verbose:$false) -or (!(Get-ChildItem -Path WSMan:\localhost\Listener))){
        Write-Output "Enabling PSRemoting"
        Enable-PSRemoting -Force -ErrorAction SilentlyContinue
    } else {
        Write-Output "PS remoting is already active."
    }

    $listeners = Get-ChildItem -Path WSMan:\localhost\Listener

    $httpListener = $listeners | where { $_.Keys -like "TRANSPORT=HTTP" }
    if ($Protocols -icontains "HTTP" -and !$httpListener) {
        Write-Output "Creating HTTP listener for hostname '$CertSubjectName'"
        [void](New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet @{Transport = 'HTTP'; Address = '*'})
        $httpListener = $listeners | where { $_.Keys -like "TRANSPORT=HTTP" }
    } elseif ($Protocols -icontains "HTTP") {
        $httpListenerEnabled = if ($httpListener) { "enabled" } else { "disabled" }
        Write-Output "HTTP listener already $httpListenerEnabled."
    }

    $httpsListener = $listeners | where { $_.Keys -like "TRANSPORT=HTTPS" }
    if ($Protocols -icontains "HTTPS" -and !$httpsListener) {
       Enable-HTTPSRemoting -CertSelfSigned:$CertSelfSigned -CertThumbprint $CertThumbprint -CertSubjectName $CertSubjectName
       $httpsListener = $listeners | where { $_.Keys -like "TRANSPORT=HTTPS" }
    } elseif ($Protocols -icontains "HTTPS") {
        $httpsListenerEnabled = if ($httpsListener) { "enabled" } else { "disabled" }
        Write-Output "HTTPS listener already $httpsListenerEnabled."
    }

    if ($Protocols -icontains "HTTPS") {
        $firewallRuleName = 'Allow WinRM HTTPS'  
        
        if (Get-Command -Name 'Get-NetFirewallRule' -ErrorAction SilentlyContinue) {
            if (!(Get-NetFirewallRule -Name $firewallRuleName -ErrorAction SilentlyContinue)) {
                Write-Output "Creating firewall rule '$firewallRuleName' for port 5986."
                [void](New-NetFirewallRule -Name $firewallRuleName -DisplayName $firewallRuleName -Action Allow -LocalPort 5986 -Profile Any -Direction Inbound -Protocol TCP)
            } else {
                Write-Output "Firewall rule '$firewallRuleName' already exists."
            }
        } else {
            $currentRule = & netsh advfirewall firewall show rule name="$firewallRuleName"
            if ($currentRule -match 'No rules match') {
                Write-Output "Creating firewall rule '$firewallRuleName' for port 5986."
                & netsh advfirewall firewall add rule name="$firewallRuleName" dir=in action=allow protocol=TCP localport=5986 profile=any
            } else {
               Write-Output "Firewall rule '$firewallRuleName' already exists."
            }
        }

    }

    if ($AuthTypes -icontains "Basic") {
        Set-AuthType -AuthType "Basic" -Enable
    }
    if ($AuthTypes -icontains "Default") {
        Set-AuthType -AuthType "Negotiate" -Enable
        Set-AuthType -AuthType "Kerberos" -Enable
    }
    if ($AuthTypes -icontains "CredSSP") {
        Set-AuthType -AuthType "CredSSP" -Enable
    }

    if ($CredSSPClientDelegateComputer) {
        Write-Output "Enabling CredSSP client role with following -DelegateComputer: $CredSSPClientDelegateComputer"
        [void](Enable-WSManCredSSP -Role Client -DelegateComputer $CredSSPClientDelegateComputer -Force)
    }

}

function Set-RenewedSelfSignedCertificate {
    
    <#
    .SYNOPSIS
    Renews self-signed certificate on HTTPS.
 
    .PARAMETER CertSubjectName
    Subject name for the certificate (if AuthTypes contains HTTPS). It must be the same string as the one used to connect to this server.
    For example if you're going to run Invoke-Command -ComputerName 192.168.1.50, subject name must be 192.168.1.50.
 
    .EXAMPLE
    Set-RenewedSelfSignedCertificate -CertSubjectName $CertSubjectName
 
    #>


    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory=$false)]
        [string]
        $CertSubjectName = $env:COMPUTERNAME
    )

    $ErrorActionPreference = "Stop"

    $selectorset = @{}
    $selectorset.add('Transport','HTTPS')
    $selectorset.add('Address','*')

    try { 
        $wsManInstance = Get-WSManInstance -ResourceUri 'winrm/config/Listener' -SelectorSet $selectorset -ErrorAction SilentlyContinue
    } catch { }
    if (!$wsManInstance) {
        throw "HTTPS has not been enabled yet. Please enable it before renewing self-seigned certificate."
    }
    $oldCertThumbprint = $wsManInstance.CertificateThumbprint
    $newCertThumbprint = Get-NewSelfSignedCertificate -CertSubjectName $CertSubjectName

    $valueset = @{}
    $valueset.add('Hostname', $CertSubjectName)
    $valueset.add('CertificateThumbprint', $newCertThumbprint)
    Write-Output "Configuring HTTPS listener."
    [void](Set-WsManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset)
    Write-Output "Certificate successfully updated from $oldCertThumbprint to $newCertThumbprint."
}


function Set-AuthType {

    <#
    .SYNOPSIS
    Enables / disables provided authentication method in PSRemoting.
     
    .PARAMETER AuthType
    Type of authentication to set.
 
    .PARAMETER Enable
    Whether to enable or disable the authentication method.
 
    .EXAMPLE
    Set-AuthType -AuthType "Basic" -Enable
    #>


    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory=$true)]
        [string] 
        [ValidateSet('Basic', 'CredSSP', 'Negotiate', 'Kerberos')]
        $AuthType,

        [Parameter(Mandatory=$false)]
        [switch] 
        $Enable
    ) 

    $authEnabled = Get-ChildItem -Path WSMan:\localhost\Service\Auth | Where-Object { $_.Name -eq $AuthType } | Select -ExpandProperty Value
    if ($Enable -and !$authEnabled) {
        Write-Output "Enabling authentication method '$AuthType'"
        Set-Item -Path "WSMan:\localhost\Service\Auth\$AuthType" -Value $true
    } elseif ($AuthTypes -inotcontains "Basic" -and $basicAuth) {
        Write-Output "Disabling authentication method '$AuthType'"
        Set-Item -Path "WSMan:\localhost\Service\Auth\$AuthType" -Value $false
    } else {
        $authEnabled = if ($Enable) { "enabled" } else { "disabled" }
        Write-Output "Authentication method '$AuthType' already $authEnabled."
    }

}

function Enable-HTTPSRemoting {
 
    <#
    .SYNOPSIS
    Enables HTTPS remoting.
     
    .PARAMETER CertSelfSigned
    If true and HTTPS is configured, a self-signed certificate will be created. Note it's validity is 365 days.
 
    .PARAMETER CertThumbprint
    Thumbprint of certificate to import (if AuthTypes contains HTTPS and CertSelfSigned is false).
 
    .PARAMETER CertSubjectName
    Subject name for the certificate (if AuthTypes contains HTTPS). It must be the same string as the one used to connect to this server.
    For example if you're going to run Invoke-Command -ComputerName 192.168.1.50, subject name must be 192.168.1.50.
 
    .PARAMETER RefreshSelfSignedCert
    If true and HTTPS is configured and self-signed certificate already exists, it will be renewed.
 
    .EXAMPLE
    Enable-HTTPSRemoting -CertSelfSigned:$CertSelfSigned -CertThumbprint $CertThumbprint -CertSubjectName $CertSubjectName
    #>


    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter(Mandatory=$false)]
        [switch] 
        $CertSelfSigned,

        [Parameter(Mandatory=$false)]
        [string] 
        $CertThumbprint,

        [Parameter(Mandatory=$true)]
        [string]
        $CertSubjectName
    )

    if ($CertSelfSigned) {
        $CertThumbprint = Get-NewSelfSignedCertificate -CertSubjectName $CertSubjectName
    }
    
    # Create the hashtables of settings to be used.
    $valueset = @{}
    $valueset.add('Hostname', $CertSubjectName)
    $valueset.add('CertificateThumbprint', $CertThumbprint)

    $selectorset = @{}
    $selectorset.add('Transport','HTTPS')
    $selectorset.add('Address','*')
    
    Write-Output "Creating HTTPS listener for hostname '$CertSubjectName'"
    [void](New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorset -ValueSet $valueset)
}

function Get-NewSelfSignedCertificate {

    <#
    .SYNOPSIS
    Creates a new self-signed certificate.
 
    .PARAMETER CertSubjectName
    Subject name for the certificate (if AuthTypes contains HTTPS). It must be the same string as the one used to connect to this server.
    For example if you're going to run Invoke-Command -ComputerName 192.168.1.50, subject name must be 192.168.1.50.
 
    .EXAMPLE
    $CertThumbprint = Get-NewSelfSignedCertificate -CertSubjectName $CertSubjectName
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory=$true)]
        [string]
        $CertSubjectName
    )

    # < Windows Server 2012 -> need legacy way to create self-signed certificate
    if ([Environment]::OSVersion.Version.Major -lt 6 -or ([Environment]::OSVersion.Version.Major -eq 6 -and [Environment]::OSVersion.Version.Minor -lt 2)) {
        $legacyOS = $true
    } else {
        $legacyOS = $false
    }

    if ($legacyOS) {
        Write-Host "Creating new self-signed certificate in legacy mode (< Windows Server 2012)"
        $CertThumbprint = New-LegacySelfSignedCert -SubjectName $CertSubjectName
    } else {
        Write-Host "Creating new self-signed certificate"
        $cert = New-SelfSignedCertificate -DnsName $CertSubjectName -CertStoreLocation "Cert:\LocalMachine\My"
        $CertThumbprint = $cert.Thumbprint
    } 

    return $CertThumbprint
}


function New-LegacySelfSignedCert {

    <#
    .SYNOPSIS
    Creates a new self-signed certificate using legacy methods.
 
    .DESCRIPTION
    Taken from script for Ansible written by Trond Hindenes <trond@hindenes.com> (Version 1.0 - July 6th, 2014)
    https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1
     
    .PARAMETER SubjectName
    Subject name for the certificate.
 
    .PARAMETER ValidDays
    Duration of certificate validity.
 
    .EXAMPLE
    $CertThumbprint = New-LegacySelfSignedCert -SubjectName $CertSubjectName
    #>

    
    [CmdletBinding()]
    [OutputType([void])]
    Param (
        [string]$SubjectName,
        [int]$ValidDays = 365
    )
    
    $name = new-object -com "X509Enrollment.CX500DistinguishedName.1"
    $name.Encode("CN=$SubjectName", 0)

    $key = new-object -com "X509Enrollment.CX509PrivateKey.1"
    $key.ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
    $key.KeySpec = 1
    $key.Length = 1024
    $key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
    $key.MachineContext = 1
    $key.Create()

    $serverauthoid = new-object -com "X509Enrollment.CObjectId.1"
    $serverauthoid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
    $ekuoids = new-object -com "X509Enrollment.CObjectIds.1"
    $ekuoids.add($serverauthoid)
    $ekuext = new-object -com "X509Enrollment.CX509ExtensionEnhancedKeyUsage.1"
    $ekuext.InitializeEncode($ekuoids)

    $cert = new-object -com "X509Enrollment.CX509CertificateRequestCertificate.1"
    $cert.InitializeFromPrivateKey(2, $key, "")
    $cert.Subject = $name
    $cert.Issuer = $cert.Subject
    $cert.NotBefore = (get-date).addDays(-1)
    $cert.NotAfter = $cert.NotBefore.AddDays($ValidDays)
    $cert.X509Extensions.Add($ekuext)
    $cert.Encode()

    $enrollment = new-object -com "X509Enrollment.CX509Enrollment.1"
    $enrollment.InitializeFromRequest($cert)
    $certdata = $enrollment.CreateRequest(0)
    $enrollment.InstallResponse(2, $certdata, 0, "")

    #return the thumprint of the last installed cert
    ls "Cert:\LocalMachine\my"| Where-Object { $_.Subject -eq "CN=$SubjectName" } | Sort-Object notbefore -Descending | select -First 1 | select -expand Thumbprint
}