Public/Start-WindowsActivation.ps1

#Requires -RunAsAdministrator
#Requires -Version 5

<#
.Synopsis
Activates Windows via KMS
.DESCRIPTION
A drop in replacement for slmgr script. By default attempts KMS activation using the
product key already installed on the machine. Use -UseKmsClientKey to also install the
KMS client setup key (GVLK) for the detected OS edition before activating. This is a
material licensing change and is therefore opt-in.
.INPUTS
string[]. You can pass the computer names
.OUTPUTS
None if successful. Throws on error.
.EXAMPLE
Start-WindowsActivation -Verbose # Activates the local computer using its existing product key
.EXAMPLE
Start-WindowsActivation -UseKmsClientKey -Verbose # Installs the GVLK for the detected OS edition then activates
.EXAMPLE
Start-WindowsActivation -Computer WS01 -Credentials (Get-Credential) # Activates WS01 over WinRM
.EXAMPLE
Start-WindowsActivation -Computer WS01, WS02 -CacheDisabled # Disables the KMS cache on WS01 and WS02
.EXAMPLE
Start-WindowsActivation -Computer WS01 -KMSServerFQDN server.domain.net -KMSServerPort 2500 # Activates against a specific KMS server
.EXAMPLE
Start-WindowsActivation -ReArm # ReArm the trial period (guard clauses apply but cannot guarantee 100% safety)
.EXAMPLE
Start-WindowsActivation -Offline -ConfirmationID 123456-123456-123456-123456-123456-123456-123456-123456-123456 # Phone activation
.LINK
https://github.com/zbalkan/slmgr-ps
#>

function Start-WindowsActivation
{
    [CmdletBinding(SupportsShouldProcess = $true,
        PositionalBinding = $false,
        ConfirmImpact = 'High',
        DefaultParameterSetName = 'ActivateWithKMS')]
    Param
    (
        # Type localhost or . for local computer or do not use the parameter
        [Parameter(Mandatory = $false,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $false)]
        [Parameter(ParameterSetName = 'ActivateWithKMS')]
        [Parameter(ParameterSetName = 'Rearm')]
        [Parameter(ParameterSetName = 'Offline')]
        [AllowNull()]
        [string[]]
        $Computer = @('localhost'),

        # Define credentials other than current user if needed
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false)]
        [Parameter(ParameterSetName = 'ActivateWithKMS')]
        [Parameter(ParameterSetName = 'Rearm')]
        [Parameter(ParameterSetName = 'Offline')]
        [AllowNull()]
        [PSCredential]
        $Credentials,

        [Parameter(Mandatory = $false,
            Position = 1,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'ActivateWithKMS')]
        [ValidateLength(6, 253)]
        [ValidateScript(
            {
                $pattern = [Regex]::new('(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)')
                if ($pattern.Matches($_).Count -gt 0)
                {
                    $true
                }
                else
                {
                    throw "$_ is invalid. Please provide a valid FQDN"
                }
            })]
        [ValidateNotNullOrEmpty()]
        [string]
        $KMSServerFQDN,

        [Parameter(Mandatory = $false,
            Position = 2,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'ActivateWithKMS')]
        [ValidateRange(1, 65535)]
        [int]
        $KMSServerPort = 1688,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'Rearm')]
        [switch]
        $Rearm,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'ActivateWithKMS')]
        [switch]
        $CacheDisabled,

        # Installs the KMS client setup key (GVLK) for the detected OS edition before
        # attempting activation. Only needed when the machine currently has a MAK or retail
        # key and must be switched to volume/KMS licensing. This is a material licensing change.
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'ActivateWithKMS')]
        [switch]
        $UseKmsClientKey,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'Offline')]
        [switch]$Offline,

        [Parameter(Mandatory = $true,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            ParameterSetName = 'Offline')]
        [ValidateScript(
            {
                # Accept 54 digits (9 groups × 6), optionally separated by dashes or spaces
                $stripped = $_ -replace '[\s\-]', ''
                if ($stripped -match '^\d{54}$')
                {
                    $true
                }
                else
                {
                    throw "$_ is not a valid Confirmation ID. Expected 54 digits (9 groups of 6), optionally separated by dashes or spaces."
                }
            })]
        [ValidateNotNullOrEmpty()]
        [string]
        $ConfirmationId

    )
    Begin
    {
        $PreviousPreference = $ErrorActionPreference
        $ErrorActionPreference = 'Stop'
        Write-Verbose 'ErrorActionPreference: Stop'
    }
    Process
    {
        Write-Verbose "Enumerating computers: $($Computer.Count) computer(s)."
        foreach ($c in $Computer)
        {
            if (-not $pscmdlet.ShouldProcess($c, "Activate Windows ($($PSCmdlet.ParameterSetName))"))
            {
                continue
            }

            Write-Verbose "Creating new CimSession for computer $c"
            $session = $null
            try
            {
                $session = Get-Session -Computer $c -Credentials $Credentials

                Write-Verbose 'Connecting to SoftwareLicensingService...'
                $service = Get-CimInstance -CimSession $session -ClassName SoftwareLicensingService

                switch ($PSCmdlet.ParameterSetName)
                {
                    'Offline'
                    {
                        Write-Verbose 'Initiating offline activation operation'
                        Invoke-OfflineActivation -CimSession $session -Service $service -ConfirmationId $ConfirmationId
                    }

                    'Rearm'
                    {
                        Write-Verbose 'Initiating ReArm operation'
                        Invoke-Rearm -CimSession $session -Service $service
                    }

                    'ActivateWithKMS'
                    {
                        if ($CacheDisabled.IsPresent)
                        {
                            Write-Verbose 'Disabling KMS host caching'
                            $service | Invoke-SppCimMethod -MethodName DisableKeyManagementServiceHostCaching
                        }

                        Write-Verbose 'Initiating KMS activation operation'
                        $kmsParams = @{ CimSession = $session; Service = $service }
                        if ($PSBoundParameters.ContainsKey('KMSServerFQDN')) { $kmsParams['KMSServerFQDN'] = $KMSServerFQDN }
                        if ($PSBoundParameters.ContainsKey('KMSServerPort')) { $kmsParams['KMSServerPort'] = $KMSServerPort }
                        if ($UseKmsClientKey.IsPresent) { $kmsParams['InstallKmsClientKey'] = $true }
                        Invoke-KMSActivation @kmsParams
                    }

                    default
                    {
                        throw 'Unknown parameter combination'
                    }
                }
            }
            catch
            {
                throw
            }
            finally
            {
                if ($null -ne $session)
                {
                    Remove-CimSession -CimSession $session -ErrorAction Ignore | Out-Null
                }
            }
        }
    }
    End
    {
        $ErrorActionPreference = $PreviousPreference
    }
}