library/xWindowsUpdate/2.8.0.0/DscResources/MSFT_xWindowsUpdate/MSFT_xWindowsUpdate.psm1

Data LocalizedData {
    # culture="en-US"e
    ConvertFrom-StringData @'
        GettingHotfixMessage=Getting the hotfix patch with ID {0}.
        ValidatingPathUri=Validating path/URI.
        ErrorPathUriSpecifiedTogether=Hotfix path and Uri parameters cannot be specified together.
        ErrorInvalidFilePathMsu=Filename provided is an invalid file path for hotfix since it does not have the msu suffix to it.
        StartKeyWord=START
        EndKeyWord= END
        FailedKeyword = FAILED
        ActionDownloadFromUri= Download from {0} using BitsTransfer.
        ActionInstallUsingwsusa = Install using wsusa.exe
        ActionUninstallUsingwsusa = Uninstall using wsusa.exe
        DownloadingPackageTo = Downloading package to filepath {0}
        FileDoesntExist=The given path {0} does not exist.
        LogNotSpecified=Log name hasn't been specified. Hotfix will use the temporary log {0} .
        ErrorOccuredOnHotfixInstall = \nCould not install the windows update. Details are stored in the log {0} . Error message is \n\n {1} .\n\nPlease look at Windows Update error codes here for more information - http://technet.microsoft.com/en-us/library/dd939837(WS.10).aspx .
        ErrorOccuredOnHotfixUninnstall = \nCould not uninstall the windows update. Details are stored in the log {0} . Error message is \n\n {1} .\n\nPlease look at Windows Update error codes here for more information - http://technet.microsoft.com/en-us/library/dd939837(WS.10).aspx .
        TestingEnsure = Testing whether hotfix is {0}.
        InvalidPath=The specified Path ({0}) is not in a valid format. Valid formats are local paths, UNC, and HTTP.
        InvalidBinaryType=The specified Path ({0}) does not appear to specify an MSU file and as such is not supported.
        TestStandardArgumentsPathWasPath = Test-StandardArguments, Path was {0}.
        NeedToDownloadFileFromSchemeDestinationWillBeDestName = Need to download file from {0}, destination will be {1}.
        TheUriSchemeWasUriScheme = The uri scheme was {0}.
        MountSharePath=Mount share to get media.
        NeedsMoreInfo=Id is required.
        InvalidIdFormat=Id must be formated either KBNNNNNNN or NNNNNNN.
        DownloadHTTPFile=Download the media over HTTP or HTTPS.
        CreatingCacheLocation = Creating cache location.
        CouldNotOpenDestFile=Cannot open the file {0} for writing.
        CreatingTheSchemeStream = Creating the {0} stream.
        SettingDefaultCredential = Setting default credential.
        SettingAuthenticationLevel = Setting authentication level.
        IgnoringBadCertificates = Ignoring bad certificates.
        GettingTheSchemeResponseStream = Getting the {0} response stream.
        ErrorOutString = Error: {0}.
        CopyingTheSchemeStreamBytesToTheDiskCache = Copying the {0} stream bytes to the disk cache.
        RedirectingPackagePathToCacheFileLocation = Redirecting package path to cache file location.
        ThePathExtensionWasPathExt = The path extension was {0}
        CreatingTheDestinationCacheFile = Creating the destination cache file.
'@

}

$CacheLocation = "$env:ProgramData\Microsoft\Windows\PowerShell\Configuration\BuiltinProvCache\MSFT_xWindowsUpdate"

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

        [parameter(Mandatory = $true)]
        [System.String]
        $Id
    )
    Set-StrictMode -Version latest

    $uri, $kbId = Test-StandardArguments -Path $Path -Id $Id

    Write-Verbose $($LocalizedData.GettingHotfixMessage -f ${Id})

    $hotfix = Get-HotFix -Id "KB$kbId"

    $returnValue = @{
        Path = ''
        Id   = $hotfix.HotFixId
        Log  = ''
    }

    $returnValue

}

$Debug = $true
Function Trace-Message {
    param([string] $Message)
    Set-StrictMode -Version latest
    if ($Debug) {
        Write-Verbose $Message
    }
}

Function New-InvalidArgumentException {
    param(
        [string] $Message,
        [string] $ParamName
    )
    Set-StrictMode -Version latest

    $exception = new-object System.ArgumentException $Message, $ParamName
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ParamName, 'InvalidArgument', $null
    throw $errorRecord
}

# The Set-TargetResource cmdlet
function Set-TargetResource {
    # should be [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "DSCMachineStatus")], but it doesn't work
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [CmdletBinding()]
    Param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [System.String]
        $Id,

        [System.String]
        $Log,

        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present',

        [pscredential] $Credential

    )
    Set-StrictMode -Version latest
    if (!$Log) {
        $Log = [IO.Path]::GetTempFileName()
        $Log += '.etl'

        Write-Verbose "$($LocalizedData.LogNotSpecified -f ${Log})"
    }
    $uri, $kbId = Test-StandardArguments -Path $Path -Id $Id



    if ($Ensure -eq 'Present') {
        $filePath = Test-WindowsUpdatePath -uri $uri -Credential $Credential
        Write-Verbose "$($LocalizedData.StartKeyWord) $($LocalizedData.ActionInstallUsingwsusa)"

        Start-Process -FilePath 'wusa.exe' -ArgumentList "`"$filepath`" /quiet /norestart /log:`"$Log`"" -Wait -NoNewWindow -ErrorAction SilentlyContinue
        $errorOccured = Get-WinEvent -Path $Log -Oldest | Where-Object { $_.Id -eq 3 }
        if ($errorOccured) {
            $errorMessage = $errorOccured.Message
            Throw "$($LocalizedData.ErrorOccuredOnHotfixInstall -f ${Log}, ${errorMessage})"
        }

        Write-Verbose "$($LocalizedData.EndKeyWord) $($LocalizedData.ActionInstallUsingwsusa)"
    } else {
        $argumentList = "/uninstall /KB:$kbId /quiet /norestart /log:`"$Log`""

        Write-Verbose "$($LocalizedData.StartKeyWord) $($LocalizedData.ActionUninstallUsingwsusa) Arguments: $ArgumentList"

        Start-Process -FilePath 'wusa.exe' -ArgumentList $argumentList  -Wait -NoNewWindow  -ErrorAction SilentlyContinue
        #Read the log and see if there was an error event
        $errorOccured = Get-WinEvent -Path $Log -Oldest | Where-Object { $_.Id -eq 3 }
        if ($errorOccured) {
            $errorMessage = $errorOccured.Message
            Throw "$($LocalizedData.ErrorOccuredOnHotfixUninstall -f ${Log}, ${errorMessage})"
        }

        Write-Verbose "$($LocalizedData.EndKeyWord) $($LocalizedData.ActionUninstallUsingwsusa)"


    }

    if (Test-Path -Path 'variable:\LASTEXITCODE') {
        if ($LASTEXITCODE -eq 3010) {
            # reboot machine if exitcode indicates reboot.
            $global:DSCMachineStatus = 1
        }
    }
}


# Function to test if Hotfix is installed.
function Test-TargetResource {
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [System.String]
        $Id,

        [System.String]
        $Log,

        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present',

        [pscredential] $Credential
    )
    Set-StrictMode -Version latest
    Write-Verbose "$($LocalizedData.TestingEnsure -f ${Ensure})"
    $uri, $kbId = Test-StandardArguments -Path $Path -Id $Id

    # This is not the correct way to test to see if an update is applicable to a machine
    # but, WUSA does not currently expose a way to ask.
    $result = Get-HotFix -Id "KB$kbId" -ErrorAction SilentlyContinue
    $returnValue = [bool]$result
    if ($Ensure -eq 'Present') {

        Return $returnValue
    } else {
        Return !$returnValue
    }

}

Function Test-StandardArguments {
    param(
        [string]
        $Path,

        [string]
        $Id
    )
    Set-StrictMode -Version latest

    Trace-Message ($LocalizedData.TestStandardArgumentsPathWasPath -f $Path)
    $uri = $null
    try {
        $uri = [uri] $Path
    } catch {
        New-InvalidArgumentException ($LocalizedData.InvalidPath -f $Path) 'Path'
    }

    if (-not @('file', 'http', 'https') -contains $uri.Scheme) {
        Trace-Message ($Localized.TheUriSchemeWasUriScheme -f $uri.Scheme)
        New-InvalidArgumentException ($LocalizedData.InvalidPath -f $Path) 'Path'
    }

    $pathExt = [System.IO.Path]::GetExtension($Path)
    Trace-Message ($LocalizedData.ThePathExtensionWasPathExt -f $pathExt)
    if (-not @('.msu') -contains $pathExt.ToLower()) {
        New-InvalidArgumentException ($LocalizedData.InvalidBinaryType -f $Path) 'Path'
    }

    if (-not $Id) {
        New-InvalidArgumentException ($LocalizedData.NeedsMoreInfo -f $Path) 'Id'
    } else {
        if ($Id -match 'kb[0-9]+') {
            if ($Matches[0] -eq $id) {
                $kbId = $id.Substring(2)
            } else {
                New-InvalidArgumentException ($LocalizedData.InvalidIdFormat -f $Path) 'Id'
            }
        } elseif ($id -match '[0-9]+') {
            if ($Matches[0] -eq $id) {
                $kbId = $id
            } else {
                New-InvalidArgumentException ($LocalizedData.InvalidIdFormat -f $Path) 'Id'
            }
        }
    }

    return @($uri, $kbId)
}


function Test-WindowsUpdatePath {
    <#
        .SYNOPSIS
        Validate path, if necessary, cache file and return the location to be accessed
    #>

    param(
        [parameter(Mandatory = $true)]
        [System.Uri] $uri,

        [pscredential] $Credential
    )
    Set-StrictMode -Version latest

    if ($uri.IsUnc) {
        $psdriveArgs = @{Name = ([guid]::NewGuid()); PSProvider = 'FileSystem'; Root = (Split-Path $uri.LocalPath) }
        if ($Credential) {
            #We need to optionally include these and then splat the hash otherwise
            #we pass a null for Credential which causes the cmdlet to pop a dialog up
            $psdriveArgs['Credential'] = $Credential
        }

        $psdrive = New-PSDrive @psdriveArgs
        $Path = Join-Path $psdrive.Root (Split-Path -Leaf $uri.LocalPath) #Necessary?
    } elseif (@('http', 'https') -contains $uri.Scheme) {
        $scheme = $uri.Scheme
        $outStream = $null
        $responseStream = $null

        try {
            Trace-Message ($LocalizedData.CreatingCacheLocation)

            if (-not (Test-Path -PathType Container $CacheLocation)) {
                mkdir $CacheLocation | Out-Null
            }

            $destName = Join-Path $CacheLocation (Split-Path -Leaf $uri.LocalPath)

            Trace-Message ($LocalizedData.NeedToDownloadFileFromSchemeDestinationWillBeDestName -f $scheme, $destName)

            try {
                Trace-Message ($LocalizedData.CreatingTheDestinationCacheFile)
                $outStream = New-Object System.IO.FileStream $destName, 'Create'
            } catch {
                #Should never happen since we own the cache directory
                Throw-TerminatingError ($LocalizedData.CouldNotOpenDestFile -f $destName) $_
            }

            try {
                Trace-Message ($LocalizedData.CreatingTheSchemeStream -f $scheme)
                $request = [System.Net.WebRequest]::Create($uri)
                Trace-Message ($LocalizedData.SettingDefaultCredential)
                $request.Credentials = [System.Net.CredentialCache]::DefaultCredentials
                if ($scheme -eq 'http') {
                    Trace-Message ($LocalizedData.SettingAuthenticationLevel)
                    # default value is MutualAuthRequested, which applies to https scheme
                    $request.AuthenticationLevel = [System.Net.Security.AuthenticationLevel]::None
                }
                if ($scheme -eq 'https') {
                    Trace-Message ($LocalizedData.IgnoringBadCertificates)
                    $request.ServerCertificateValidationCallBack = { $true }
                }
                Trace-Message ($LocalizedData.GettingTheSchemeResponseStream -f $scheme)
                $responseStream = (([System.Net.HttpWebRequest]$request).GetResponse()).GetResponseStream()
            } catch {
                Trace-Message ($LocalizedData.ErrorOutString -f ($_ | Out-String))
                Throw-TerminatingError ($LocalizedData.CouldNotGetHttpStream -f $scheme, $Path) $_
            }

            try {
                Trace-Message ($LocalizedData.CopyingTheSchemeStreamBytesToTheDiskCache -f $scheme)
                $responseStream.CopyTo($outStream)
                $responseStream.Flush()
                $outStream.Flush()
            } catch {
                Trace-Message ($LocalizedData.ErrorOutString -f ($_ | Out-String))
                Throw-TerminatingError ($LocalizedData.ErrorCopyingDataToFile -f $Path, $destName) $_
            }
        } finally {
            if ($outStream) {
                $outStream.Close()
            }

            if ($responseStream) {
                $responseStream.Close()
            }
        }
        Trace-Message ($LocalizedData.RedirectingPackagePathToCacheFileLocation)
        $Path = $downloadedFileName = $destName
    }
    return $Path
}

Export-ModuleMember -Function *-TargetResource