DSCResources/Grani_PendingReboot/Grani_PendingReboot.psm1

#region Initialize

function Initialize
{
    $script:TempPath = "$env:LOCALAPPDATA\Temp\{0}" -f [Guid]::NewGuid().ToString()
    $script:pendingSec = 20

    # Enum for Ensure
    Add-Type -TypeDefinition @"
        public enum EnsureType
        {
            Present,
            Absent
        }
"@
 -ErrorAction SilentlyContinue

    # Enum for RebootTrigger
    Add-Type -TypeDefinition @"
        public enum RebootTriggerType
        {
            ComponentBasedServicing,
            WindowsUpdate,
            PendingFileRename,
            PendingComputerRename,
            CcmClientSDK
        }
"@
 -ErrorAction SilentlyContinue
}

Initialize

#endregion

#region Message Definition

$verboseMessages = Data {
    ConvertFrom-StringData -StringData @"
        SetLCMStatus = Setting the DSCMachineStatus global variable to 1.
        WaitTimeSecDetect = WaitTimeSec detected. Waiting {0}sec before enable reboot flag.
        PendingRebootUntilDSCReboot = Pending Reboot for {0}sec for when RebootIfNeeded is $true. Even $false will reboot until time past.
"@

}

$debugMessages = Data {
    ConvertFrom-StringData -StringData @"
        ChangeLCMRebootNodeIfNeeded = Changing Local Configuration Manager RebootNodeIfNeeded status from '{0}' to '{1}'
        PendingRebootDetect = A pending reboot was found for : {0}.
        SCCMCanNotQueryException = Unable to query CCM_ClientUtilities: {0}
        SkipTriggerChecking = {0}Trigger detect $false. Skipping Trigger check.
"@

}

$errorMessages = Data {
    ConvertFrom-StringData -StringData @"
"@

}

#endregion

#region *-TargetResource

function Get-TargetResource
{
    [OutputType([System.Collections.Hashtable])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]$Name,

        [parameter(Mandatory = $false)]
        [System.UInt32]$WaitTimeSec = 0,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Force = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$WhatIf = $false,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerComponentBasedServicing = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerWindowsUpdate = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingFileRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingComputerRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerCcmClientSDK = $true
    )

    # return hash
    $returnValue = @{
        Name = $Name
        WaitTimeSec = $WaitTimeSec
        Force = $Force
        WhatIf = $WhatIf
        TriggerComponentBasedServicing = $TriggerComponentBasedServicing
        TriggerWindowsUpdate = $TriggerWindowsUpdate
        TriggerPendingFileRename = $TriggerPendingFileRename
        TriggerPendingComputerRename = $TriggerPendingComputerRename
        TriggerCcmClientSDK = $TriggerCcmClientSDK
    }

    # initialize
    $scriptBlocks = @()

    # Windows Component Reboot Check
    if ($TriggerComponentBasedServicing)
    {
        $scriptBlocks += @{
            [RebootTriggerType]::ComponentBasedServicing.ToString() = {
                (Split-Path (Get-ChildItem 'registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\').Name -Leaf) -contains "RebootPending"
            }
        }
    }
    else
    {
        Write-Debug ($debugMessages.SkipTriggerChecking -f [RebootTriggerType]::ComponentBasedServicing.ToString())
        $returnValue.([RebootTriggerType]::ComponentBasedServicing.ToString()) = $false
    }

    # Windows Update Reboot Check
    if ($TriggerWindowsUpdate)
    {
        $scriptBlocks += @{
            [RebootTriggerType]::WindowsUpdate.ToString() = {
                (Split-Path (Get-ChildItem 'registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\').Name -Leaf) -contains "RebootRequired"
            }
        }
    }
    else
    {
        Write-Debug ($debugMessages.SkipTriggerChecking -f [RebootTriggerType]::WindowsUpdate.ToString())
        $returnValue.([RebootTriggerType]::WindowsUpdate.ToString()) = $false
    }

    # PendingFileRename
    if ($TriggerPendingFileRename)
    {
        $scriptBlocks += @{
            [RebootTriggerType]::PendingFileRename.ToString() = {
                (Get-ItemProperty 'registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\').PendingFileRenameOperations.Length -gt 0
            }
        }
    }
    else
    {
        Write-Debug ($debugMessages.SkipTriggerChecking -f [RebootTriggerType]::PendingFileRename.ToString())
        $returnValue.([RebootTriggerType]::PendingFileRename.ToString()) = $false
    }

    # Computer Name Reboot Check
    if ($TriggerPendingComputerRename)
    {
        $scriptBlocks += @{
            [RebootTriggerType]::PendingComputerRename.ToString() = {
                $ActiveComputerName = (Get-ItemProperty 'registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName').ComputerName
                $PendingComputerName = (Get-ItemProperty 'registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName').ComputerName
                $ActiveComputerName -ne $PendingComputerName
            }
        }
    }
    else
    {
        Write-Debug ($debugMessages.SkipTriggerChecking -f [RebootTriggerType]::PendingComputerRename.ToString())
        $returnValue.([RebootTriggerType]::PendingComputerRename.ToString()) = $false
    }

    # System Center Reboot Check
    if ($TriggerCcmClientSDK)
    {
        $scriptBlocks += @{
            [RebootTriggerType]::CcmClientSDK.ToString() = {
                try
                {
                    $CCMSplat = @{
                        NameSpace='ROOT\ccm\ClientSDK'
                        Class='CCM_ClientUtilities'
                        Name='DetermineIfRebootPending'
                        ErrorAction='Stop'
                    }
                    $CCMClientSDK = Invoke-WmiMethod @CCMSplat
                    ($CCMClientSDK.ReturnValue -eq 0) -and ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
                }
                catch
                {
                    $false
                    Write-Debug ($debugMessages.SCCMCanNotQueryException -f $_)
                }
            }
        }
    }
    else
    {
        Write-Debug ($debugMessages.SkipTriggerChecking -f [RebootTriggerType]::CcmClientSDK.ToString())
        $returnValue.([RebootTriggerType]::CcmClientSDK.ToString()) = $false
    }

    # execute Each Parameter
    $ensure = [EnsureType]::Present
    if (($scriptBlocks | measure).Count -ne 0)
    {
        foreach ($script in $scriptBlocks.Keys)
        {
            $returnValue.$script = & $scriptBlocks."$script"
            If ($returnValue.$script)
            {
                Write-Debug ($debugMessages.PendingRebootDetect -f $script)
                $Ensure = [EnsureType]::Absent
            }
        }
    }
   
    # ensure
    $returnValue.Ensure = $ensure

    # return
    return $returnValue
}


function Set-TargetResource
{
    [OutputType([Void])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]$Name,

        [parameter(Mandatory = $false)]
        [System.UInt32]$WaitTimeSec = 0,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Force = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$WhatIf = $false,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerComponentBasedServicing = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerWindowsUpdate = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingFileRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingComputerRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerCcmClientSDK = $true
    )

    # Sleep for WaitTimeSec
    SleepWaitTimeSec -WaitTimeSec $WaitTimeSec

    # Set DSCMachineStatus
    Write-Verbose $verboseMessages.SetLCMStatus
    $global:DSCMachineStatus = 1

    # Manual Reboot
    ManualReboot -Force $Force -WhatIf $WhatIf
}


function Test-TargetResource
{
    [OutputType([System.Boolean])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]$Name,

        [parameter(Mandatory = $false)]
        [System.UInt32]$WaitTimeSec = 0,

        [parameter(Mandatory = $false)]
        [System.Boolean]$Force = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$WhatIf = $false,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerComponentBasedServicing = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerWindowsUpdate = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingFileRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerPendingComputerRename = $true,

        [parameter(Mandatory = $false)]
        [System.Boolean]$TriggerCcmClientSDK = $true
    )

    return (Get-TargetResource -Name $Name -WaitTime $WaitTime -Force $Force -WhatIf $WhatIf -TriggerComponentBasedServicing $TriggerComponentBasedServicing -TriggerWindowsUpdate $TriggerWindowsUpdate -TriggerPendingFileRename $TriggerPendingFileRename -TriggerPendingComputerRename $TriggerPendingComputerRename -TriggerCcmClientSDK $TriggerCcmClientSDK).Ensure -eq [EnsureType]::Present.ToString()
}

#endregion

#region Set Helper

function SleepWaitTimeSec ([Uint32]$WaitTimeSec)
{
    if ($WaitTimeSec -ne 0)
    {
        Write-Verbose ($verboseMessages.WaitTimeSecDetect -f $WaitTimeSec)
        [System.Threading.Thread]::Sleep([TimeSpan]::FromSeconds($WaitTimeSec))
    }
}

function ManualReboot ($Force, $WhatIf)
{
    if (-not $WhatIf)
    {
        Write-Verbose ($verboseMessages.PendingRebootUntilDSCReboot -f $script:pendingSec)
        [System.Threading.Thread]::Sleep([TimeSpan]::FromSeconds($script:pendingSec))
    }
    Restart-Computer -Force:$Force -WhatIf:$WhatIf
}

#endregion

Export-ModuleMember -Function *-TargetResource