Public/Invoke-Shutdown.ps1

#Requires -Module CimCmdlets
function global:Invoke-Shutdown
{
    <#
        .EXTERNALHELP HelperFunctions.psm1-Help.xml
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([int])]
    param
    (
    [Parameter(Mandatory = $true,
             ValueFromPipeline = $true,
             HelpMessage = 'Enter the computer FQDN(s)')]
    [string[]]$ComputerName,
    [Parameter(Mandatory = $true,
             HelpMessage = 'Select the Shutdown Type.')]
    [ValidateSet('Logoff', 'Shutdown', 'Reboot', 'PowerOff')]
    [string]$ShutdownType,
    [Parameter(Mandatory = $false,
             HelpMessage = 'Add this switch to force a reboot or shutdown or logoff')]
    [switch]$Force,
    [Parameter(Mandatory = $false,
             HelpMessage = 'Enter the $Timeout value in seconds')]
    [uint32]$Wait,
    [Parameter(Mandatory = $false,
             HelpMessage = 'Enter the reason for this action')]
    [Alias('Message')]
    [string]$Comment = "A remote shutdown was initiated by $([Environment]::UserName).",
    [Parameter(Mandatory = $true,
             HelpMessage = 'Enter a valued value from within the NOTES section')]
    [ShutDown_MajorReason]$MajorReasonCode,
    [Parameter(Mandatory = $true,
             HelpMessage = 'Enter the Minor value from the NOTES section')]
    [ShutDown_MinorReason]$MinorReasonCode,
    [Parameter(Mandatory = $false,
             HelpMessage = 'Select this switch if this was an unplanned action')]
    [switch]$Unplanned,
    [Parameter(Mandatory = $false,
             HelpMessage = 'Specify administrator credentials for the computer.')]
    [System.Management.Automation.PsCredential]$Credential
    )

    begin
    {
        $Error.Clear()
        $ns = 'root\CIMv2'

        try
        {
            #https://docs.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=netcore-2.0#System_Net_SecurityProtocolType_SystemDefault
            if ($PSVersionTable.PSVersion.Major -lt 6 -and [Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12')
            {
                Write-Verbose -Message 'Adding support for TLS 1.2'
                [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12
            }
        }
        catch
        {
            Write-Warning -Message 'Adding TLS 1.2 to supported security protocols was unsuccessful.'
        }

        if ($PSBoundParameters.ContainsKey('Force'))
        {
            $Flags = ([ShutDownType]$ShutdownType).value__ + 4
        }
        else
        {
            $Flags = ([ShutDownType]$ShutdownType).value__
        }

        $PlannedReasonCode = (0x80000000) * -1
        if ($PSBoundParameters.ContainsKey('Unplanned'))
        {
            $ReasonCode = $MajorReasonCode.value__ + $MinorReasonCode.value__
        }
        else
        {
            $ReasonCode = $MajorReasonCode.value__ + $MinorReasonCode.value__ + $PlannedReasonCode
        }

        $params = @{
            Flags     = $Flags
            Comment    = $Comment
            ReasonCode = $ReasonCode
        }

        if ($PSBoundParameters.ContainsKey("Wait"))
        {
            $params.Add("Timeout", $Wait)
        }
        else
        {
            $params.Add("Timeout", [uint32]30)
        }

    }
    process
    {
        foreach ($computer in $ComputerName)
        {
            try
            {
                Write-Verbose ("Testing Connection to {0}..." -f $computer)

                $tcParams = @{
                    ComputerName = $computer
                    Count       = 1
                    Quiet       = $true
                }

                if (($PSBoundParameters.ContainsKey("Credential")) -and ($null -ne $PSBoundParameters["Credential"])) { $tcParams.Add("Credential", $Credential) }
                if ((Test-Connection @tcParams) -eq $true)
                {

                    if ($computer -eq ([System.Net.Dns]::GetHostByName("LocalHost").HostName))
                    {
                        try
                        {
                            try
                            {
                                $osParams = @{
                                    Class             = "Win32_OperatingSystem"
                                    Namespace             = $ns
                                    EnableAllPrivileges = $true
                                    ErrorAction         = 'Stop'
                                }

                                if (($PSBoundParameters.ContainsKey("Credential")) -and ($null -ne $PSBoundParameters["Credential"])) { $osParams.Add("Credential", $Credential) }
                                $OS = Get-WmiObject $osParams
                                if ($PSCmdlet.ShouldProcess($computer, "Execute shutdown/reboot process on $($computer)"))
                                {
                                    $result = $OS.Win32ShutdownTracker($Timeout, $Comment, $ReasonCode, $Flags)
                                }

                            }
                            catch
                            {
                                $errorMessage = "{0}: {1}" -f $Error[0], $Error[0].InvocationInfo.PositionMessage
                                Write-Error $errorMessage -ErrorAction Continue
                            }

                            if ($PSBoundParameters.ContainsKey('Verbose'))
                            {
                                switch ($result.ReturnValue)
                                {

                                    0 {
                                        Write-Verbose "$($MyInvocation.InvocationName) on $computer processed successfully."
                                    }
                                    1190 {
                                        Write-Verbose "$($MyInvocation.InvocationName) on $computer returned error code 1190. A system shutdown has already been scheduled."
                                    }
                                    1191 {
                                        Write-Verbose "$($MyInvocation.InvocationName) on $computer returned error code 1191. A user is still logged into the system. Use the -Force parameter if necessary."
                                    }
                                    default {
                                        Write-Verbose "$($MyInvocation.InvocationName) on $computer returned error code $result. System Error Codes can be found here: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes"
                                    }

                                }
                            }

                            return $result.ReturnValue
                        }
                        catch
                        {
                            $errorMessage = "{0}: {1}" -f $Error[0], $Error[0].InvocationInfo.PositionMessage
                            Write-Error $errorMessage -ErrorAction Continue
                        }
                    }
                    else
                    {
                        try
                        {
                            if (($PSBoundParameters.ContainsKey('Credential')) -and ($null -ne $PSBoundParameters["Credential"]))
                            {
                                $session = New-CimSession -ComputerName $computer -Credential $Credential -Authentication Negotiate -SkipTestConnection -ErrorAction Stop
                            }
                            else
                            {
                                $session = New-CimSession -ComputerName $computer -SkipTestConnection -ErrorAction Stop
                            }

                        }
                        catch
                        {
                            try
                            {
                                Write-Information ("Unable to connect to {0} using WSMan, attempting to use DCOM protocol instead" -f $computer)
                                if (($PSBoundParameters.ContainsKey('Credential')) -and ($null -ne $PSBoundParameters["Credential"]))
                                {
                                    $session = New-CimSession -ComputerName $computer -Credential $Credential -Authentication Negotiate -SessionOption (New-CimSessionOption -Protocol Dcom) -SkipTestConnection -ErrorAction Stop
                                }
                                else
                                {
                                    $session = New-CimSession -ComputerName $computer -SessionOption (New-CimSessionOption -Protocol Dcom) -SkipTestConnection -ErrorAction Stop
                                }
                            }
                            catch
                            {
                                $errorMessage = "Unable to connect to {0} with WSMan or DCOM protocols" -f $computer
                                Write-Error -Message $errorMessage -ErrorAction Continue
                            }
                        }

                        if ($null -ne $session.Name)
                        {
                            try
                            {
                                $OS = Get-CimInstance -ClassName Win32_OperatingSystem -Namespace $ns -CimSession $session -ErrorAction Stop

                                if ($PSCmdlet.ShouldProcess($computer, "Execute shutdown method on $($computer)"))
                                {
                                    if ($PSBoundParameters.ContainsKey('Force'))
                                    {
                                        Invoke-CimMethod -CimInstance $OS -MethodName Win32ShutdownTracker -Arguments $params -CimSession $session
                                        if ($? -eq $true)
                                        {
                                            $result = "0"
                                            if ($result -eq 0)
                                            {
                                                [PSCustomObject]@{
                                                    ComputerName = $computer
                                                    ShutdownType = $ShutdownType
                                                    ReasonCode   = "$($MajorReasonCode): $MinorReasonCode"
                                                    CommandSuccessful = $true
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        $result = (Invoke-CimMethod -CimInstance $OS -MethodName Win32ShutdownTracker -Arguments $params -CimSession $session).ReturnValue
                                        if ($result -eq 0)
                                        {
                                            [PSCustomObject]@{
                                                ComputerName = $computer
                                                ShutdownType = $ShutdownType
                                                ReasonCode   = "$($MajorReasonCode): $MinorReasonCode"
                                                CommandSuccessful = $true
                                            }
                                        }
                                    }
                                }

                            }
                            catch
                            {
                                $errorMessage = "{0}: {1}" -f $Error[0], $Error[0].InvocationInfo.PositionMessage
                                Write-Error $errorMessage -ErrorAction Continue
                            }

                            Remove-CimSession -CimSession $session
                        }

                    }

                }
                else
                {
                    Write-Error ("{0} is not currently available. No shutdown request processed" -f $computer) -Category ConnectionError -ErrorVariable +connectionErrors
                    return -1
                }

            }
            catch [System.Management.Automation.MethodInvocationException]
            {

                Write-Verbose "Generic Failure may be caused if 'ShutdownType' of 'Logoff' was used and no users were logged in at the time."
                Write-Error $_
                return -2

            }
            catch
            {

                Write-Verbose "Error Type is $($_.Exception.GetType().FullName)"
                Write-Error $_
                return -256

            }

        }

    }
    end
    {
        Write-Verbose ("Invoke-Shutdown processing complete. There were {0} connection errors." -f $connectionErrors.Count)
    }
} #end Invoke-Shutdown