xDscDiagnostics.psm1

#Region './prefix.ps1' 0
<####################################################################################################################################################
 # This script enables a user to diagnose errors caused by a DSC operation. In short, the following commands would help you diagnose errors
 # To get the last 10 operations in DSC that show their Result status (failure , success) : Get-xDscOperation
 # To get a list of last n (say, 13) DSC operations : Get-xDscOperation -Newest 13
 # To see details of the last operation : Trace-xDscOperation
 # TO view trace details of the third last operation run : Trace-xDscOperation 3
 # To view trace details of an operation with Job ID $jID : Trace-xDscOperation -JobID $jID
 # To View trace details of multiple computers : Trace-xDscOperation -ComputerName @("PN25113D0891","PN25113D0890")
 # To enable the debug event channel for DSC : Update-xDscEventLogStatus -Channel Debug -Status Enabled
 # To enable the analytic event channel for DSC on another computer (say, with name ABC) : Update-xDscEventLogStatus -Channel Analytic -Status Enabled -ComputerName ABC
 # To disable the analytic event channel for DSC on another computer (say, with name ABC) : Update-xDscEventLogStatus -Channel Analytic -Status Disabled -ComputerName ABC
 #####################################################################################################################################################>


#region Global variables
$script:DscVerboseEventIdsAndPropertyIndex = @{
    4100 = 3
    4117 = 2
    4098 = 3
}
$script:DscLogName = "Microsoft-windows-dsc"
$script:RedirectOutput = $false
$script:TemporaryHtmLocation = "$env:TEMP/dscreport"
$script:SuccessResult = "Success"
$script:FailureResult = "Failure"
$script:ThisCredential = ""
$script:ThisComputerName = $env:COMPUTERNAME
$script:UsingComputerName = $false
$script:FormattingFile = "xDscDiagnosticsFormat.ps1xml"
$script:RunFirstTime = $true
#endregion

#region Cache for events
$script:LatestGroupedEvents = @{ } #Hashtable of "Computername", "GroupedEvents"
$script:LatestEvent = @{ }          #Hashtable of "ComputerName", "LatestEvent logged"
#endregion

$script:azureDscExtensionTargetName = 'Azure DSC Extension'
$script:dscTargetName = 'DSC Node'
$script:windowsTargetName = 'Windows'
$script:dscPullServerTargetName = 'DSC Pull Server'
$script:validTargets = @($script:azureDscExtensionTargetName, $script:dscTargetName, $script:windowsTargetName, $script:dscPullServerTargetName)
$script:defaultTargets = @($script:azureDscExtensionTargetName, $script:dscTargetName, $script:windowsTargetName)

$script:datapointTypeName = 'xDscDiagnostics.DataPoint'
$script:dataPoints = @{
    AzureVmAgentLogs       = @{
        Description = 'Logs from the Azure VM Agent, including all extensions'
        Target      = $script:azureDscExtensionTargetName
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            Copy-Item -Recurse C:\WindowsAzure\Logs $tempPath\WindowsAzureLogs -ErrorAction SilentlyContinue
        }
    } # end data point
    DSCExtension           = @{
        Description = @'
The state of the Azure DSC Extension, including the configuration(s),
configuration data (but not any decryption keys), and included or
generated files.
'@

        Target      = $script:azureDscExtensionTargetName
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            $dirs = @(Get-ChildItem -Path C:\Packages\Plugins\Microsoft.Powershell.*DSC -ErrorAction SilentlyContinue)
            $dir = $null
            if ($dirs.Count -ge 1)
            {
                $dir = $dirs[0].FullName
            }

            if ($dir)
            {
                Write-Verbose -message "Found DSC extension at: $dir" -verbose
                Copy-Item -Recurse $dir $tempPath\DscPackageFolder -ErrorAction SilentlyContinue
                Get-ChildItem "$tempPath\DscPackageFolder" -Recurse | % {
                    if ($_.Extension -ieq '.msu' -or ($_.Extension -ieq '.zip' -and $_.BaseName -like 'Microsoft.Powershell*DSC_*.*.*.*'))
                    {
                        $newFileName = "$($_.FullName).wasHere"
                        Get-ChildItem $_.FullName | Out-String | Out-File $newFileName -Force
                        $_.Delete()
                    }
                }
            }
            else
            {
                Write-Verbose -message 'Did not find DSC extension.' -verbose
            }
        }
    } # end data point
    DscEventLog            = @{
        Description = 'The DSC event log.'
        EventLog    = 'Microsoft-Windows-DSC/Operational'
        Target      = $script:dscTargetName
    } # end data point
    ApplicationEventLog    = @{
        Description = 'The Application event log.'
        EventLog    = 'Application'
        Target      = $script:windowsTargetName
    } # end data point
    SystemEventLog         = @{
        Description = 'The System event log.'
        EventLog    = 'System'
        Target      = $script:windowsTargetName
    } # end data point
    PullServerEventLog     = @{
        Description = 'The DSC Pull Server event log.'
        EventLog    = 'Microsoft-Windows-PowerShell-DesiredStateConfiguration-PullServer/Operational'
        Target      = $script:dscPullServerTargetName
    } # end data point
    ODataEventLog          = @{
        Description = 'The Management OData event log (used by the DSC Pull Server).'
        EventLog    = 'Microsoft-Windows-ManagementOdataService/Operational'
        Target      = $script:dscPullServerTargetName
    } # end data point
    IisBinding             = @{
        Description = 'The Iis Bindings.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            $width = 900
            Get-WebBinding |
                Select-Object protocol, bindingInformation, sslFlags, ItemXPath |
                    Out-String -Width $width |
                        Out-File -FilePath $tempPath\IisBindings.txt -Width $width
        }
        Target      = $script:dscPullServerTargetName
    } # end data point
    HttpErrLogs            = @{
        Description = 'The HTTPERR logs.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            mkdir $tempPath\HttpErr > $null
            Copy-Item $env:windir\System32\LogFiles\HttpErr\*.* $tempPath\HttpErr -ErrorAction SilentlyContinue
        }
        Target      = $script:dscPullServerTargetName
    } # end data point
    IISLogs                = @{
        Description = 'The IIS logs.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            Import-Module WebAdministration
            $logFolder = (Get-WebConfigurationProperty "/system.applicationHost/sites/siteDefaults" -name logfile.directory).Value
            mkdir $tempPath\Inetlogs > $null
            Copy-Item (Join-Path $logFolder *.*) $tempPath\Inetlogs -ErrorAction SilentlyContinue
        }
        Target      = $script:dscPullServerTargetName
    } # end data point
    ServicingLogs          = @{
        Description = 'The Windows Servicing logs, including, WindowsUpdate, CBS and DISM logs.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            mkdir $tempPath\CBS > $null
            mkdir $tempPath\DISM > $null
            Copy-Item $env:windir\WindowsUpdate.log $tempPath\WindowsUpdate.log -ErrorAction SilentlyContinue
            Copy-Item $env:windir\logs\CBS\*.* $tempPath\CBS -ErrorAction SilentlyContinue
            Copy-Item $env:windir\logs\DISM\*.* $tempPath\DISM -ErrorAction SilentlyContinue
        }
        Target      = $script:windowsTargetName
    } # end data point
    HotfixList             = @{
        Description = 'The output of Get-Hotfix'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            Get-HotFix | Out-String | Out-File  $tempPath\HotFixIds.txt
        }
        Target      = $script:windowsTargetName
    } # end data point
    GetLcmOutput           = @{
        Description = 'The output of Get-DscLocalConfigurationManager'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            $dscLcm = Get-DscLocalConfigurationManager
            $dscLcm | Out-String | Out-File   $tempPath\Get-dsclcm.txt
            $dscLcm | ConvertTo-Json -Depth 10 | Out-File   $tempPath\Get-dsclcm.json
        }
        Target      = $script:dscTargetName
    } # end data point
    VersionInformation     = @{
        Description = 'The PsVersionTable and OS version information'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            $PSVersionTable | Out-String | Out-File   $tempPath\psVersionTable.txt
            Get-CimInstance win32_operatingSystem | select version | out-string | Out-File   $tempPath\osVersion.txt
        }
        Target      = $script:windowsTargetName
    } # end data point
    CertThumbprints        = @{
        Description = 'The local machine cert thumbprints.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            dir Cert:\LocalMachine\My\ | select -ExpandProperty Thumbprint | out-string | out-file $tempPath\LocalMachineCertThumbprints.txt
        }
        Target      = $script:windowsTargetName
    } # end data point
    DscResourceInventory   = @{
        Description = 'The name, version and path to installed dsc resources.'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            Get-DscResource 2> $tempPath\ResourceErrors.txt | select name, version, path | out-string | out-file $tempPath\ResourceInfo.txt
        }
        Target      = $script:dscTargetName
    } # end data point
    DscConfigurationStatus = @{
        Description = 'The output of Get-DscConfigurationStatus -all'
        ScriptBlock = {
            param ($tempPath)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest
            $statusCommand = get-Command -name Get-DscConfigurationStatus -ErrorAction SilentlyContinue
            if ($statusCommand)
            {
                Get-DscConfigurationStatus -All | out-string | Out-File   $tempPath\get-dscconfigurationstatus.txt
            } }
        Target      = $script:dscTargetName
    } # end data point
}
#EndRegion './prefix.ps1' 236
#Region './Private/Add-ClassTypes.ps1' 0
#Function to Output errors, verbose messages or warning
function Add-ClassTypes
{
    #We don't want to add the same types again and again.
    if ($script:RunFirstTime)
    {
        $pathToFormattingFile = (Join-Path  $PSScriptRoot $script:FormattingFile)
        $ClassDefinitionGroupedEvents = @"
            using System;
            using System.Globalization;
            using System.Collections;
            namespace Microsoft.PowerShell.xDscDiagnostics
            {
                public class GroupedEvents {
                        public int SequenceId;
                        public System.DateTime TimeCreated;
                        public string ComputerName;
                        public Guid? JobID = null;
                        public System.Array AllEvents;
                        public int NumberOfEvents;
                        public System.Array AnalyticEvents;
                        public System.Array DebugEvents;
                        public System.Array NonVerboseEvents;
                        public System.Array VerboseEvents;
                        public System.Array OperationalEvents;
                        public System.Array ErrorEvents;
                        public System.Array WarningEvents;
                        public string Result;

                   }
            }
"@

        $ClassDefinitionTraceOutput = @"
               using System;
               using System.Globalization;
               namespace Microsoft.PowerShell.xDscDiagnostics
               {
                   public enum EventType {
                        DEBUG,
                        ANALYTIC,
                        OPERATIONAL,
                        ERROR,
                        VERBOSE
                   }
                   public class TraceOutput {
                        public EventType EventType;
                        public System.DateTime TimeCreated;
                        public string Message;
                        public string ComputerName;
                        public Guid? JobID = null;
                        public int SequenceID;
                        public System.Diagnostics.Eventing.Reader.EventRecord Event;
                   }
               }

"@

        Add-Type -Language CSharp -TypeDefinition $ClassDefinitionGroupedEvents
        Add-Type -Language CSharp -TypeDefinition $ClassDefinitionTraceOutput
        #Update-TypeData -TypeName TraceOutput -DefaultDisplayPropertySet EventType, TimeCreated, Message
        Update-FormatData  -PrependPath $pathToFormattingFile

        $script:RunFirstTime = $false; #So it doesnt do it the second time.
    }
}
#EndRegion './Private/Add-ClassTypes.ps1' 64
#Region './Private/Clear-DscDiagnosticsCache.ps1' 0
function Clear-DscDiagnosticsCache
{
    LogDscDiagnostics -Verbose "Clearing Diagnostics Cache"
    $script:LatestGroupedEvents = @{ }
    $script:LatestEvent = @{ }
}
#EndRegion './Private/Clear-DscDiagnosticsCache.ps1' 6
#Region './Private/Collect-DataPoint.ps1' 0
# attempts to Collect a datapoint
# Returns $true if it believes it collected the datapoint
function Collect-DataPoint
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [String] $Name,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [HashTable] $dataPoint,

        [Parameter(Mandatory = $true)]
        [HashTable] $invokeCommandParams
    )

    $collected = $false
    if ($dataPoint.ScriptBlock)
    {
        Write-Verbose -Message "Collecting '$name-$($dataPoint.Description)' using ScripBlock ..."
        Invoke-Command -ErrorAction:Continue @invokeCommandParams -script $dataPoint.ScriptBlock -argumentlist @($tempPath)
        $collected = $true
    }

    if ($dataPoint.EventLog)
    {
        Write-Verbose -Message "Collecting '$name-$($dataPoint.Description)' using Eventlog ..."
        try
        {
            Export-EventLog -Name $dataPoint.EventLog -Path $tempPath @invokeCommandParams
        }
        catch
        {
            Write-Warning "Collecting '$name-$($dataPoint.Description)' failed with the following error:$([System.Environment]::NewLine)$_"
        }

        $collected = $true
    }
    return $collected
}
#EndRegion './Private/Collect-DataPoint.ps1' 42
#Region './Private/Copy-ToZipFileUsingShell.ps1' 0
# Copy files using the Shell.
#
# Note, because this uses shell this will not work on core OSs
# But we only use this on older OSs and in test, so core OS use
# is unlikely
function Copy-ToZipFileUsingShell
{
    param
    (
        [string]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {
                if ($_ -notlike '*.zip')
                {
                    throw 'zipFileName must be *.zip'
                }
                else
                {
                    return $true
                }
            })]
        $zipfilename,

        [string]
        [ValidateScript( {
                if (-not (Test-Path $_))
                {
                    throw 'itemToAdd must exist'
                }
                else
                {
                    return $true
                }
            })]
        $itemToAdd,

        [switch]
        $overWrite
    )
    Set-StrictMode -Version latest
    if (-not (Test-Path $zipfilename) -or $overWrite)
    {
        set-content $zipfilename ('PK' + [char]5 + [char]6 + ("$([char]0)" * 18))
    }
    $app = New-Object -com shell.application
    $zipFile = ( Get-Item $zipfilename ).fullname
    $zipFolder = $app.namespace( $zipFile )
    $itemToAdd = (Resolve-Path $itemToAdd).ProviderPath
    $zipFolder.copyhere( $itemToAdd )
}
#EndRegion './Private/Copy-ToZipFileUsingShell.ps1' 50
#Region './Private/Export-EventLog.ps1' 0
#
# Exports an event log to a file in the path specified
# on the specified session, if the session is not specified
# a session to the local machine will be used
#
function Export-EventLog
{
    [CmdletBinding()]
    param
    (
        [string] $Name,
        [string] $path,
        [System.Management.Automation.Runspaces.PSSession] $Session
    )
    Write-Verbose "Exporting eventlog $name"
    $local = $false
    $invokeCommandParams = @{ }
    if ($Session)
    {
        $invokeCommandParams.Add('Session', $Session);
    }
    else
    {
        $local = $true
    }

    invoke-command -ErrorAction:Continue @invokeCommandParams -script {
        param ($name, $path)
        $ErrorActionPreference = 'stop'
        Set-StrictMode -Version latest
        Write-Debug "Name: $name"

        Write-Debug "Path: $path"
        Write-Debug "windir: $Env:windir"
        $exePath = Join-Path $Env:windir 'system32\wevtutil.exe'
        $exportFileName = "$($Name -replace '/','-').evtx"

        $ExportCommand = "$exePath epl '$Name' '$Path\$exportFileName' /ow:True 2>&1"
        Invoke-expression -command $ExportCommand
    } -argumentlist @($Name, $path)
}
#EndRegion './Private/Export-EventLog.ps1' 41
#Region './Private/Get-AllDscEvents.ps1' 0
#Function to get all dsc events in the event log - not exposed by the module
function Get-AllDscEvents
{
    #If you want a specific channel events, run it as Get-AllDscEvents
    param
    (
        [string[]]$ChannelType = @("Debug" , "Analytic" , "Operational") ,
        $OtherParameters = @{ }
    )

    if ($ChannelType.ToLower().Contains("operational"))
    {
        $operationalEvents = Get-WinEvent -LogName "$script:DscLogName/operational"  @OtherParameters -ea Ignore
        $allEvents = $operationalEvents
    }

    if ($ChannelType.ToLower().Contains("analytic"))
    {
        $analyticEvents = Get-WinEvent -LogName "$script:DscLogName/analytic" -Oldest  -ea Ignore @OtherParameters
        if ($analyticEvents -ne $null)
        {
            #Convert to an array type before adding another type - to avoid the error "Method invocation failed with no op_addition operator"
            $allEvents = [System.Array]$allEvents + $analyticEvents
        }
    }

    if ($ChannelType.ToLower().Contains("debug"))
    {
        $debugEvents = Get-WinEvent -LogName "$script:DscLogName/debug" -Oldest -ea Ignore @OtherParameters
        if ($debugEvents -ne $null)
        {
            $allEvents = [System.Array]$allEvents + $debugEvents

        }
    }

    return $allEvents
}
#EndRegion './Private/Get-AllDscEvents.ps1' 38
#Region './Private/Get-AllGroupedDscEvents.ps1' 0
function Get-AllGroupedDscEvents
{
    $groupedEvents = $null
    $latestEvent = Get-LatestEvent
    LogDscDiagnostics -Verbose "Collecting all events from the DSC logs"
    if ($script:LatestEvent[$script:ThisComputerName])
    {
        #Check if there were any differences between the latest event and the latest event in th ecache
        $compareResult = Compare-Object $script:LatestEvent[$script:ThisComputerName] $latestEvent -Property TimeCreated, Message
        #Compare object result will be null if they're both equal
        if (($compareResult -eq $null) -and $script:LatestGroupedEvents[$script:ThisComputerName])
        {
            # this means no new events were generated and you can use the event cache.
            $groupedEvents = $script:LatestGroupedEvents[$script:ThisComputerName]
            return $groupedEvents
        }

    }
    #if cache needs to be replaced, it will not return in the previous line and will come here.

    #Save it to cache
    $allEvents = Get-AllDscEvents
    if (!$allEvents)
    {
        LogDscDiagnostics -Error "Error : Could not find any events. Either a DSC operation has not been run, or the event logs are turned off . Please ensure the event logs are turned on in DSC. To set an event log, run the command wevtutil Set-Log <channelName> /e:true, example: wevtutil set-log 'Microsoft-Windows-Dsc/Operational' /e:true /q:true"
        return
    }
    $groupedEvents = $allEvents | Group-Object {
        $_.Properties[0].Value
    }

    $script:LatestEvent[$script:ThisComputerName] = $latestEvent
    $script:LatestGroupedEvents[$script:ThisComputerName] = $groupedEvents


    #group based on their Job Ids
    return $groupedEvents
}
#EndRegion './Private/Get-AllGroupedDscEvents.ps1' 38
#Region './Private/Get-DscErrorMessage.ps1' 0
function Get-DscErrorMessage
{
    param (<#[System.Diagnostics.Eventing.Reader.EventRecord[]]#>$ErrorRecords)
    $cimErrorId = 4131

    $errorText = ""
    foreach ($Record in $ErrorRecords)
    {
        #go through each record, and get the single error message required for that record.
        $outputErrorMessage = Get-SingleRelevantErrorMessage -errorEvent $Record
        if ($Record.Id -eq $cimErrorId)
        {
            $errorText = "$outputErrorMessage $errorText"
        }
        else
        {
            $errorText = "$errorText $outputErrorMessage"
        }
    }
    return  $errorText

}
#EndRegion './Private/Get-DscErrorMessage.ps1' 22
#Region './Private/Get-DscLatestJobId.ps1' 0
#Gets the JOB ID of the most recently executed script.
function Get-DscLatestJobId
{
    #Collect operational events , they're ordered from newest to oldest.

    $allEvents = Get-WinEvent -LogName "$script:DscLogName/operational" -MaxEvents 2 -ea Ignore
    if ($allEvents -eq $null)
    {
        return "NOJOBID"
    }
    $latestEvent = $allEvents[0] #Since it extracts it in a sorted order.

    #Extract just the jobId from the string like : Job : {<jobid>}
    #$jobInfo = (((($latestEvent.Message -split (":",2))[0] -split "job {")[1]) -split "}")[0]
    $jobInfo = $latestEvent.Properties[0].value

    return $jobInfo.ToString()
}
function Get-LatestEvent
{
    $allEvents = Get-WinEvent -LogName "$script:DscLogName/operational" -MaxEvents 2 -ea Ignore
    if ($allEvents -eq $null)
    {
        return "NOEVENT"
    }
    $latestEvent = $allEvents[0] #Since it extracts it in a sorted order.
    return $latestEvent
}
#EndRegion './Private/Get-DscLatestJobId.ps1' 28
#Region './Private/Get-DscOperationInternal.ps1' 0
#Internal function called by Get-xDscOperation
function Get-DscOperationInternal
{
    param
    (
        [UInt32]$Newest = 10
    )
    #Groupo all events
    $groupedEvents = Get-AllGroupedDscEvents

    $DiagnosedGroup = $groupedEvents

    #Define the type that you want the output in

    $index = 1
    foreach ($singleRecordInGroupedEvents in $DiagnosedGroup)
    {
        $singleOutputRecord = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $singleRecordInGroupedEvents -index $index
        $singleOutputRecord
        if ($index -ge $Newest)
        {
            break;
        }
        $index++
    }
}
#EndRegion './Private/Get-DscOperationInternal.ps1' 26
#Region './Private/Get-FolderAsZip.ps1' 0
#
# Zips the specified folder
# returns either the path or the contents of the zip files based on the returnvalue parameterer
# When using the contents, Use set-content to create a zip file from it.
# on the specified session, if the session is not specified
# a session to the local machine will be used
#
#
function Get-FolderAsZip
{
    [CmdletBinding()]
    param
    (
        [string]$sourceFolder,
        [string] $destinationPath,
        [System.Management.Automation.Runspaces.PSSession] $Session,
        [ValidateSet('Path', 'Content')]
        [string] $ReturnValue = 'Path',
        [string] $filename
    )

    $local = $false
    $invokeCommandParams = @{ }
    if ($Session)
    {
        $invokeCommandParams.Add('Session', $Session);
    }
    else
    {
        $local = $true
    }

    $attempts = 0
    $gotZip = $false
    while ($attempts -lt 5 -and !$gotZip)
    {
        $attempts++
        $resultTable = invoke-command -ErrorAction:Continue @invokeCommandParams -script {
            param ($logFolder, $destinationPath, $fileName, $ReturnValue)
            $ErrorActionPreference = 'stop'
            Set-StrictMode -Version latest


            $tempPath = Join-path $env:temp ([system.io.path]::GetRandomFileName())
            if (!(Test-Path $tempPath))
            {
                mkdir $tempPath > $null
            }

            $sourcePath = Join-path $logFolder '*'
            Copy-Item -Recurse $sourcePath $tempPath -ErrorAction SilentlyContinue

            $content = $null
            $caughtError = $null
            try
            {
                # Generate an automatic filename if filename is not supplied
                if (!$fileName)
                {
                    $fileName = "$([System.IO.Path]::GetFileName($logFolder))-$((Get-Date).ToString('yyyyMMddhhmmss')).zip"
                }

                if ($destinationPath)
                {
                    $zipFile = Join-Path $destinationPath $fileName

                    if (!(Test-Path $destinationPath))
                    {
                        mkdir $destinationPath > $null
                    }
                }
                else
                {
                    $zipFile = Join-Path ([IO.Path]::GetTempPath()) ('{0}.zip' -f $fileName)
                }

                # Choose appropriate implementation based on CLR version
                if ($PSVersionTable.CLRVersion.Major -lt 4)
                {
                    Copy-ToZipFileUsingShell -zipfilename $zipFile -itemToAdd $tempPath
                    $content = Get-Content $zipFile | Out-String
                }
                else
                {
                    Add-Type -AssemblyName System.IO.Compression.FileSystem > $null
                    [IO.Compression.ZipFile]::CreateFromDirectory($tempPath, $zipFile) > $null
                    $content = Get-Content -Raw $zipFile
                }
            }
            catch [Exception]
            {
                $caughtError = $_
            }

            if ($ReturnValue -eq 'Path')
            {
                # Don't return content if we don't need it
                return @{
                    Content     = $null
                    Error       = $caughtError
                    zipFilePath = $zipFile
                }
            }
            else
            {
                return @{
                    Content     = $content
                    Error       = $caughtError
                    zipFilePath = $zipFile
                }
            }
        } -argumentlist @($sourceFolder, $destinationPath, $fileName, $ReturnValue) -ErrorVariable zipInvokeError


        if ($zipInvokeError -or $resultTable.Error)
        {
            if ($attempts -lt 5)
            {
                Write-Debug "An error occured trying to zip $sourceFolder . Will retry..."
                Start-Sleep -Seconds $attempts
            }
            else
            {
                if ($resultTable.Error)
                {
                    $lastError = $resultTable.Error
                }
                else
                {
                    $lastError = $zipInvokeError[0]
                }

                Write-Warning "An error occured trying to zip $sourceFolder . Aborting."
                Write-ErrorInfo -ErrorObject $lastError -WriteWarning

            }
        }
        else
        {
            $gotZip = $true
        }
    }

    if ($ReturnValue -eq 'Path')
    {
        $result = $resultTable.zipFilePath
    }
    else
    {
        $result = $resultTable.content
    }

    return $result
}
#EndRegion './Private/Get-FolderAsZip.ps1' 154
#Region './Private/Get-MessageFromEvent.ps1' 0
function Get-MessageFromEvent($EventRecord , [switch]$verboseType)
{
    #You need to remove the job ID and send back the message
    if ($EventRecord.Id -in $script:DscVerboseEventIdsAndPropertyIndex.Keys -and $verboseType)
    {
        $requiredIndex = $script:DscVerboseEventIdsAndPropertyIndex[$($EventRecord.Id)]
        return $EventRecord.Properties[$requiredIndex].Value
    }

    $NonJobIdText = ($EventRecord.Message -split ([Environment]::NewLine , 2))[1]


    return $NonJobIdText
}
#EndRegion './Private/Get-MessageFromEvent.ps1' 14
#Region './Private/Get-SingleDscOperation.ps1' 0
#This function gets all the DSC runs that are recorded into the event log.
function Get-SingleDscOperation
{
    #If you specify a sequence ID, then the diagnosis will be for that sequence ID.
    param
    (
        [Uint32]$indexInArray = 0,
        [Guid]$JobId
    )

    #Get all events
    $groupedEvents = Get-AllGroupedDscEvents
    if (!$groupedEvents)
    {
        return
    }
    #If there is a job ID present, ignore the IndexInArray, search based on jobID
    if ($JobId)
    {
        LogDscDiagnostics -Verbose "Looking at Event Trace for the given Job ID $JobId"
        $indexInArray = 0;
        foreach ($eventGroup in $groupedEvents)
        {
            #Check if the Job ID is present in any
            if ($($eventGroup.Name) -match $JobId)
            {
                break;
            }
            $indexInArray ++
        }

        if ($indexInArray -ge $groupedEvents.Count)
        {
            #This means the job id doesn't exist
            LogDscDiagnostics -Error "The Job ID Entered $JobId, does not exist among the dsc operations. To get a list of previously run DSC operations, run this command : Get-xDscOperation"
            return
        }
    }

    $requiredRecord = $groupedEvents[$indexInArray]

    if ($requiredRecord -eq $null)
    {
        LogDscDiagnostics -Error "Could not obtain the required record! "
        return
    }
    $errorText = "[None]"
    $thisRunsOutputEvents = Split-SingleDscGroupedRecord -singleRecordInGroupedEvents $requiredRecord -index $indexInArray

    $thisRunsOutputEvents
}
#EndRegion './Private/Get-SingleDscOperation.ps1' 51
#Region './Private/Get-SingleRelevantErrorMessage.ps1' 0
function Get-SingleRelevantErrorMessage(<#[System.Diagnostics.Eventing.Reader.EventRecord]#>$errorEvent)
{
    $requiredPropertyIndex = @{
        4116 = 2;
        4131 = 1;
        4183 = -1; #means full message
        4129 = -1;
        4192 = -1;
        4193 = -1;
        4194 = -1;
        4185 = -1;
        4097 = 6;
        4103 = 5;
        4104 = 4
    }
    $cimErrorId = 4131
    $errorText = ""
    $outputErrorMessage = ""
    $eventId = $errorEvent.Id
    $propertyIndex = $requiredPropertyIndex[$eventId]
    if ($propertyIndex -and $propertyIndex -ne -1)
    {
        #This means You need just the property from the indices hash
        $outputErrorMessage = $errorEvent.Properties[$propertyIndex].Value
    }
    else
    {
        $outputErrorMessage = Get-MessageFromEvent -EventRecord $errorEvent
    }
    return $outputErrorMessage

}
#EndRegion './Private/Get-SingleRelevantErrorMessage.ps1' 32
#Region './Private/Get-WinEvent.ps1' 0
#Wrapper over Get-WinEvent, that will call into a computer if required.
function Get-WinEvent
{
    $resultArray = ""
    try
    {
        if ($script:UsingComputerName)
        {
            if ($script:ThisCredential)
            {
                $resultArray = Microsoft.PowerShell.Diagnostics\Get-WinEvent @args -ComputerName $script:ThisComputerName -Credential $script:ThisCredential
            }
            else
            {
                $resultArray = Microsoft.PowerShell.Diagnostics\Get-WinEvent @args -ComputerName $script:ThisComputerName
            }
        }
        else
        {
            $resultArray = Microsoft.PowerShell.Diagnostics\Get-WinEvent @args
        }
    }
    catch
    {
        LogDscDiagnostics -Error "Get-Winevent failed with error : $_ "
        throw "Cannot read events from computer $script:ThisComputerName. Please check if the firewall is enabled. Run this command in the remote machine to enable firewall for remote administration : New-NetFirewallRule -Name 'Service RemoteAdmin' -Action Allow "
    }

    return $resultArray
}
#EndRegion './Private/Get-WinEvent.ps1' 30
#Region './Private/LogDscDiagnostics.ps1' 0

function LogDscDiagnostics
{
    param ($text , [Switch]$Error , [Switch]$Verbose , [Switch]$Warning)
    $formattedText = "XDscDiagnostics : $text"
    if ($Error)
    {
        Write-Error   $formattedText
    }
    elseif ($Verbose)
    {
        Write-Verbose $formattedText
    }

    elseif ($Warning)
    {
        Write-Warning $formattedText
    }

}
#EndRegion './Private/LogDscDiagnostics.ps1' 20
#Region './Private/Split-SingleDscGroupedRecord.ps1' 0
function Split-SingleDscGroupedRecord
{
    param
    (
        $singleRecordInGroupedEvents,
        $index)

    #$singleOutputRecord = New-Object psobject
    $status = $script:SuccessResult
    $errorEvents = @()
    $col_AllEvents = @()
    $col_verboseEvents = @()
    $col_analyticEvents = @()
    $col_debugEvents = @()
    $col_operationalEvents = @()
    $col_warningEvents = @()
    $col_nonVerboseEvents = @()

    #We want to now add a column for each event that says "staus as success or failure"
    $oneGroup = $singleRecordInGroupedEvents.Group
    $column_Time = $oneGroup[0].TimeCreated
    $oneGroup |
        % {
            $thisEvent = $_
            $thisType = ""
            $timeCreatedOfEvent = $_.TimeCreated

            if ($_.level -eq 2) #which means there's an error
            {
                $status = "$script:FailureResult"
                $errorEvents += $_
                $thisType = [Microsoft.PowerShell.xDscDiagnostics.EventType]::ERROR

            }
            elseif ($_.LevelDisplayName -like "warning")
            {
                $col_warningEvents += $_
            }
            if ($_.ContainerLog.endsWith("operational"))
            {
                $col_operationalEvents += $_ ;
                $col_nonVerboseEvents += $_

                #Only if its not an error message, mark it as OPerational tag
                if (!$thisType)
                {
                    $thisType = [Microsoft.PowerShell.xDscDiagnostics.EventType]::OPERATIONAL
                }
            }
            elseif ($_.ContainerLog.endsWith("debug"))
            {
                $col_debugEvents += $_ ; $thisType = [Microsoft.PowerShell.xDscDiagnostics.EventType]::DEBUG
            }
            elseif ($_.ContainerLog.endsWith("analytic"))
            {
                $col_analyticEvents += $_
                if ($_.Id -in $script:DscVerboseEventIdsAndPropertyIndex.Keys)
                {
                    $col_verboseEvents += $_
                    $thisType = [Microsoft.PowerShell.xDscDiagnostics.EventType]::VERBOSE

                }
                else
                {
                    $col_nonVerboseEvents += $_
                    $thisType = [Microsoft.PowerShell.xDscDiagnostics.EventType]::ANALYTIC

                }
            }
            $eventMessageFromEvent = Get-MessageFromEvent $thisEvent -verboseType
            #Add event with its tag

            $thisObject = New-Object PSobject -Property @{
                TimeCreated = $timeCreatedOfEvent
                EventType = $thisType
                Event = $thisEvent
                Message = $eventMessageFromEvent
            }
            $defaultProperties = @('TimeCreated' , 'Message' , 'EventType')
            $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet' , [string[]]$defaultProperties)
            $defaultMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
            $thisObject | Add-Member MemberSet PSStandardMembers $defaultMembers

            $col_AllEvents += $thisObject

        }

    $jobIdWithoutParenthesis = ($($singleRecordInGroupedEvents.Name).split('{}'))[1] #Remove paranthesis that comes in the job id
    if (!$jobIdWithoutParenthesis)
    {
        $jobIdWithoutParenthesis = $null
    }

    $singleOutputRecord = New-Object Microsoft.PowerShell.xDscDiagnostics.GroupedEvents -property @{
        SequenceID     = $index;
        ComputerName   = $script:ThisComputerName;
        JobId          = $jobIdWithoutParenthesis;
        TimeCreated    = $column_Time;
        Result         = $status;
        NumberOfEvents = $singleRecordInGroupedEvents.Count;
    }

    $singleOutputRecord.AllEvents = $col_AllEvents | Sort-Object TimeCreated;
    $singleOutputRecord.AnalyticEvents = $col_analyticEvents ;
    $singleOutputRecord.WarningEvents = $col_warningEvents | Sort-Object TimeCreated ;
    $singleOutputRecord.OperationalEvents = $col_operationalEvents;
    $singleOutputRecord.DebugEvents = $col_debugEvents ;
    $singleOutputRecord.VerboseEvents = $col_verboseEvents  ;
    $singleOutputRecord.NonVerboseEvents = $col_nonVerboseEvents | Sort-Object TimeCreated;
    $singleOutputRecord.ErrorEvents = $errorEvents;

    return $singleOutputRecord
}
#EndRegion './Private/Split-SingleDscGroupedRecord.ps1' 113
#Region './Private/Test-ContainerParameter.ps1' 0
#
# Tests if a parameter is a container, to be used in a ValidateScript attribute
#
function Test-ContainerParameter
{
    [CmdletBinding()]
    param
    (
        [string] $Path,
        [string] $Name = 'Path'
    )

    if (!(Test-Path $Path -PathType Container))
    {
        throw "$Name parameter must be a valid container."
    }

    return $true
}
#EndRegion './Private/Test-ContainerParameter.ps1' 19
#Region './Private/Test-DscEventLogStatus.ps1' 0
# Function to prompt the user to set an event log, for the channel passed in as parameter
function Test-DscEventLogStatus
{
    param ($Channel = "Analytic")
    $LogDetails = Get-WinEvent -ListLog "$script:DscLogName/$Channel"
    if ($($LogDetails.IsEnabled))
    {
        return $true
    }
    LogDscDiagnostics -Warning "The $Channel log is not enabled. To enable it, please run the following command: `n Update-xDscEventLogStatus -Channel $Channel -Status Enabled `nFor more help on this cmdlet run Get-Help Update-xDscEventLogStatus"

    return $false
}
#EndRegion './Private/Test-DscEventLogStatus.ps1' 13
#Region './Private/Test-PullServerPresent.ps1' 0
#
# Verifies if Pull Server is installed on this machine
#
function Test-PullServerPresent
{
    [CmdletBinding()]

    $isPullServerPresent = $false;

    $isServerSku = Test-ServerSku

    if ($isServerSku)
    {
        Write-Verbose "This is a Server machine"
        $website = Get-WebSite PSDSCPullServer -erroraction silentlycontinue
        if ($website -ne $null)
        {
            $isPullServerPresent = $true
        }
    }

    Write-Verbose "This is not a pull server"
    return $isPullServerPresent
}
#EndRegion './Private/Test-PullServerPresent.ps1' 24
#Region './Private/Test-ServerSku.ps1' 0
#
# Checks if this machine is a Server SKU
#
function Test-ServerSku
{
    [CmdletBinding()]
    $os = Get-CimInstance -ClassName  Win32_OperatingSystem
    $isServerSku = ($os.ProductType -ne 1)
}
#EndRegion './Private/Test-ServerSku.ps1' 9
#Region './Private/Trace-DscOperationInternal.ps1' 0
function Trace-DscOperationInternal
{
    [cmdletBinding()]
    param
    (
        [UInt32]$SequenceID = 1, #latest is by default
        [Guid]$JobId

    )


    #region VariableChecks
    $indexInArray = ($SequenceId - 1); #Since it is indexed from 0

    if ($indexInArray -lt 0)
    {
        LogDscDiagnostics -Error "Please enter a valid Sequence ID . All sequence IDs can be seen after running command Get-xDscOperation . " -ForegroundColor Red
        return
    }
    $null = Test-DscEventLogStatus -Channel "Analytic"
    $null = Test-DscEventLogStatus -Channel "Debug"

    #endregion

    #First get the whole object set of that operation
    $thisRUnsOutputEvents = ""
    if (!$JobId)
    {
        $thisRunsOutputEvents = Get-SingleDscOperation -IndexInArray $indexInArray
    }
    else
    {
        $thisRunsOutputEvents = Get-SingleDscOperation -IndexInArray $indexInArray -JobId $JobId
    }
    if (!$thisRunsOutputEvents)
    {
        return;
    }

    #Now we play with it.
    $result = $thisRunsOutputEvents.Result

    #Parse the error events and store them in error text.
    $errorEvents = $thisRunsOutputEvents.ErrorEvents
    $errorText = Get-DscErrorMessage -ErrorRecords  $errorEvents

    #Now Get all logs which are non verbose
    $nonVerboseMessages = @()

    $allEventMessageObject = @()
    $thisRunsOutputEvents.AllEvents |
        % {
            $ThisEvent = $_.Event
            $ThisMessage = $_.Message
            $ThisType = $_.EventType
            $ThisTimeCreated = $_.TimeCreated
            #Save a hashtable as a message value
            if (!$thisRunsOutputEvents.JobId)
            {
                $thisJobId = $null
            }
            else
            {
                $thisJobId = $thisRunsOutputEvents.JobId
            }
            $allEventMessageObject += New-Object Microsoft.PowerShell.xDscDiagnostics.TraceOutput -Property @{
                EventType = $ThisType
                TimeCreated = $ThisTimeCreated
                Message = $ThisMessage
                ComputerName = $script:ThisComputerName
                JobID = $thisJobId
                SequenceID = $SequenceID
                Event = $ThisEvent
            }
        }

    return $allEventMessageObject

}
#EndRegion './Private/Trace-DscOperationInternal.ps1' 79
#Region './Private/Write-ProgressMessage.ps1' 0
function Write-ProgressMessage
{
    [CmdletBinding()]
    param ([string]$Status, [int]$PercentComplete, [switch]$Completed)

    Write-Progress -Activity 'Get-AzureVmDscDiagnostics' @PSBoundParameters
    Write-Verbose -message $status
}
#EndRegion './Private/Write-ProgressMessage.ps1' 8
#Region './Public/Get-xDscConfigurationDetail.ps1' 0
# Gets the Json details for a configuration status
function Get-xDscConfigurationDetail
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValuefromPipeline = $true, ParameterSetName = "ByValue")]
        [ValidateScript( {
                if ($_.CimClass.CimClassName -eq 'MSFT_DSCConfigurationStatus')
                {
                    return $true
                }
                else
                {
                    throw 'Must be a configuration status object'
                }
            })]
        $ConfigurationStatus,

        [Parameter(Mandatory = $true, ParameterSetName = "ByJobId")]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {
                [System.Guid] $jobGuid = [System.Guid]::Empty
                if ([System.Guid]::TryParse($_, ([ref] $jobGuid)))
                {
                    return $true
                }
                else
                {
                    throw 'JobId must be a valid GUID'
                }
            })]
        [string] $JobId
    )
    process
    {
        [bool] $hasJobId = $false
        [string] $id = ''
        if ($null -ne $ConfigurationStatus)
        {
            $id = $ConfigurationStatus.JobId
        }
        else
        {
            [System.Guid] $jobGuid = [System.Guid]::Parse($JobId)
            # ensure the job id string has the expected leading and trailing '{', '}' characters.
            $id = $jobGuid.ToString('B')
        }

        $detailsFiles = Get-ChildItem -Path "$env:windir\System32\Configuration\ConfigurationStatus\$id-?.details.json" -ErrorAction 'SilentlyContinue'
        if ($detailsFiles)
        {
            foreach ($detailsFile in $detailsFiles)
            {
                Write-Verbose -Message "Getting details from: $($detailsFile.FullName)"
                (Get-Content -Encoding Unicode -raw $detailsFile.FullName) |
                    ConvertFrom-Json |
                        Foreach-Object {
                            Write-Output $_
                        }
            }
        }
        elseif ($null -ne $ConfigurationStatus)
        {
            if ($($ConfigurationStatus.type) -eq 'Consistency')
            {
                Write-Warning -Message "DSC does not produced details for job type: $($ConfigurationStatus.type); id: $($ConfigurationStatus.JobId)"
            }
            else
            {
                Write-Error -Message "Cannot find detail for job type: $($ConfigurationStatus.type); id: $($ConfigurationStatus.JobId)"
            }
        }
        else
        {
            throw "Cannot find configuration details for job $id"
        }
    }
}
#EndRegion './Public/Get-xDscConfigurationDetail.ps1' 79
#Region './Public/Get-xDscDiagnosticsZipDataPoint.ps1' 0
# Returns a list of datapoints which will be collected by
# New-xDscDiagnosticsZip
function Get-xDscDiagnosticsZipDataPoint
{
    foreach ($key in $script:dataPoints.Keys)
    {
        $dataPoint = $script:dataPoints.$key
        $dataPointObj = ([PSCustomObject] @{
            Name = $key
            Description = $dataPoint.Description
            Target = $dataPoint.Target
        })
        $dataPointObj.pstypenames.Clear()
        $dataPointObj.pstypenames.Add($script:datapointTypeName)
        Write-Output $dataPointObj
    }
}
#EndRegion './Public/Get-xDscDiagnosticsZipDataPoint.ps1' 17
#Region './Public/Get-xDscOperation.ps1' 0
<#
.SYNOPSIS
Gives a list of all DSC operations that were executed . Each DSC operation has sequence Id information , and job id information
It returns a list of objects, each of which contain information on a distinct DSC operation . Here a DSC operation is referred to any single DSC execution, such as start-dscconfiguration, test-dscconfiguration etc. These will log events with a unique jobID (guid) identifying the DSC operation.

When you run Get-xDscOperation, you will see a list of past DSC operations , and you could use the following details from the output to trace any of them individually.
- Job ID : By using this GUID, you can search for the events in Event viewer, or run Trace-xDscOperation -jobID <required Jobid> to obtain all event details of that operation
- Sequence Id : By using this identifier, you could run Trace-xDscOperation <sequenceId> to get all event details of that particular dsc operation.


.DESCRIPTION
This will list all the DSC operations that were run in the past in the computer. By Default, it will list last 10 operations.

.PARAMETER Newest
By default 10 last DSC operations are pulled out from the event logs. To have more, you could use enter another number with this parameter.a PS Object with all the information output to the screen can be navigated by the user as required.


.EXAMPLE
Get-xDscOperation 20
Lists last 20 operations

.EXAMPLE
Get-xDscOperation -ComputerName @("XYZ" , "ABC") -Credential $cred
Lists operations for the array of computernames passed in.
#>


function Get-xDscOperation
{
    [cmdletBinding()]
    param
    (
        [UInt32]$Newest = 10,
        [String[]]$ComputerName,
        [pscredential]$Credential
    )
    Add-ClassTypes
    if ($ComputerName)
    {
        $script:UsingComputerName = $true
        $args = $PSBoundParameters
        $null = $args.Remove("ComputerName")
        $null = $args.Remove("Credential")

        foreach ($thisComputerName in $ComputerName)
        {
            LogDscDiagnostics -Verbose "Gathering logs for Computer $thisComputerName"
            $script:ThisComputerName = $thisComputerName
            $script:ThisCredential = $Credential
            Get-DscOperationInternal  @PSBoundParameters

        }
    }
    else
    {
        $script:ThisComputerName = $env:COMPUTERNAME
        Get-DscOperationInternal @PSBoundParameters
        $script:UsingComputerName = $false
    }
}
#EndRegion './Public/Get-xDscOperation.ps1' 59
#Region './Public/New-xDscDiagnosticsZip.ps1' 0
#
# Gathers diagnostics for DSC and the DSC Extension into a zipfile
# if specified, in the specified path
# if specified, in the specified filename
# on the specified session, if the session is not specified
# a session to the local machine will be used
#
function New-xDscDiagnosticsZip
{
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'default')]
    [Alias('Get-xDscDiagnosticsZip')]
    param
    (
        [Parameter(ParameterSetName = 'default')]
        [Parameter(ParameterSetName = 'includedDataPoints')]
        [Parameter(ParameterSetName = 'includedTargets')]
        [System.Management.Automation.Runspaces.PSSession] $Session,

        [Parameter(ParameterSetName = 'default')]
        [Parameter(ParameterSetName = 'includedDataPoints')]
        [Parameter(ParameterSetName = 'includedTargets')]
        [string] $destinationPath,

        [Parameter(ParameterSetName = 'default')]
        [Parameter(ParameterSetName = 'includedDataPoints')]
        [Parameter(ParameterSetName = 'includedTargets')]
        [string] $filename,

        [Parameter(ParameterSetName = 'includedDataPoints', Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({
            foreach ($point in $_)
            {
                if ($_.pstypenames -notcontains $script:datapointTypeName)
                {
                    throw 'IncluedDataPoint must be an array of xDscDiagnostics datapoint objects.'
                }
            }

            return $true
        })]
        [object[]] $includedDataPoint
    )
    dynamicparam
    {
        $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()

        $dataPointTargetsParametereAttribute = [System.Management.Automation.ParameterAttribute]::new()
        $dataPointTargetsParametereAttribute.Mandatory = $true
        $dataPointTargetsParametereAttribute.ParameterSetName = 'includedTargets'
        $attributeCollection.Add($dataPointTargetsParametereAttribute)

        $validateSetAttribute = [System.Management.Automation.ValidateSetAttribute]::new([string[]]$script:validTargets)

        $attributeCollection.Add($validateSetAttribute)
        $dataPointTargetsParam = New-Object System.Management.Automation.RuntimeDefinedParameter('DataPointTarget', [String[]], $attributeCollection)

        $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
        $paramDictionary.Add('DataPointTarget', $dataPointTargetsParam)
        return $paramDictionary
    }

    process
    {
        [string[]] $dataPointTarget = $PSBoundParameters.DataPointTarget
        $dataPointsToCollect = @{ }
        switch ($pscmdlet.ParameterSetName)
        {
            "includedDataPoints"
            {
                foreach ($dataPoint in $includedDataPoint)
                {
                    $dataPointsToCollect.Add($dataPoint.Name, $script:dataPoints.($dataPoint.Name))
                }
            }
            "includedTargets"
            {
                foreach ($key in $script:dataPoints.keys)
                {
                    $dataPoint = $script:dataPoints.$key
                    if ($dataPointTarget -icontains $dataPoint.Target)
                    {
                        $dataPointsToCollect.Add($key, $dataPoint)
                    }
                }
            }
            default
            {
                foreach ($key in $script:dataPoints.keys)
                {
                    $dataPoint = $script:dataPoints.$key
                    if ($script:defaultTargets -icontains $dataPoint.Target)
                    {
                        $dataPointsToCollect.Add($key, $dataPoint)
                    }
                }
            }
        }

        $local = $false
        $invokeCommandParams = @{ }
        if ($Session)
        {
            $invokeCommandParams.Add('Session', $Session);
        }
        else
        {
            $local = $true
        }

        $privacyConfirmation = "Collecting the following information, which may contain private/sensative details including:"
        foreach ($key in $dataPointsToCollect.Keys)
        {
            $dataPoint = $dataPointsToCollect.$key
            $privacyConfirmation += [System.Environment]::NewLine
            $privacyConfirmation += ("`t{0}" -f $dataPoint.Description)
        }
        $privacyConfirmation += [System.Environment]::NewLine
        $privacyConfirmation += "This tool is provided for your convience, to ensure all data is collected as quickly as possible."
        $privacyConfirmation += [System.Environment]::NewLine
        $privacyConfirmation += "Are you sure you want to continue?"

        if ($pscmdlet.ShouldProcess($privacyConfirmation))
        {
            $tempPath = invoke-command -ErrorAction:Continue @invokeCommandParams -script {
                $ErrorActionPreference = 'stop'
                Set-StrictMode -Version latest
                $tempPath = Join-path $env:temp ([system.io.path]::GetRandomFileName())
                if (!(Test-Path $tempPath))
                {
                    mkdir $tempPath > $null
                }
                return $tempPath
            }
            Write-Verbose -message "tempPath: $tempPath"

            $collectedPoints = 0
            foreach ($key in $dataPointsToCollect.Keys)
            {
                $dataPoint = $dataPointsToCollect.$key
                if (!$dataPoint.Skip -or !(&$dataPoint.skip))
                {
                    Write-ProgressMessage  -Status "Collecting '$($dataPoint.Description)' ..." -PercentComplete ($collectedPoints / $script:dataPoints.Count)
                    $collected = Collect-DataPoint -dataPoint $dataPoint -invokeCommandParams $invokeCommandParams -Name $key
                    if (!$collected)
                    {
                        Write-Warning "Did not collect '$($dataPoint.Description)'"
                    }
                }
                else
                {
                    Write-Verbose -Message "Skipping collecting '$($dataPoint.Description)' ..."
                }
                $collectedPoints ++
            }

            if (!$destinationPath)
            {
                Write-ProgressMessage  -Status 'Getting destinationPath ...' -PercentComplete 74
                $destinationPath = invoke-command -ErrorAction:Continue @invokeCommandParams -script {
                    $ErrorActionPreference = 'stop'
                    Set-StrictMode -Version latest
                    Join-path $env:temp ([system.io.path]::GetRandomFileName())
                }
            }

            Write-Debug -message "destinationPath: $destinationPath" -verbose
            $zipParams = @{
                sourceFolder    = $tempPath
                destinationPath = $destinationPath
                Session         = $session
                fileName        = $fileName
            }

            Write-ProgressMessage  -Status 'Zipping files ...' -PercentComplete 75
            if ($local)
            {
                $zip = Get-FolderAsZip @zipParams
                $zipPath = $zip
            }
            else
            {
                $zip = Get-FolderAsZip @zipParams -ReturnValue 'Content'
                if (!(Test-Path $destinationPath))
                {
                    mkdir $destinationPath > $null
                }
                $zipPath = (Join-path $destinationPath "$($session.ComputerName)-dsc-diags-$((Get-Date).ToString('yyyyMMddhhmmss')).zip")
                set-content -path $zipPath -value $zip
            }

            Start-Process $destinationPath
            Write-Verbose -message "Please send this zip file the engineer you have been working with. The engineer should have emailed you instructions on how to do this: $zipPath" -verbose
            Write-ProgressMessage  -Completed
            return $zipPath
        }

    }
}
#EndRegion './Public/New-xDscDiagnosticsZip.ps1' 199
#Region './Public/Trace-xDscOperation.ps1' 0
<#
.SYNOPSIS
Traces through any DSC operation selected from among all operations using its unique sequence ID (obtained from Get-xDscOperation), or from its unique Job ID

.DESCRIPTION
This function, when called, will look through all the event logs for DSC, and output the results in the form of an object, that contains the event type, event message, time created, computer name, job id, sequence number, and the event information.

.PARAMETER SequenceId
Each operation in DSC has a certain Sequence ID, ordered by time of creation of these DSC operations. The sequence IDs can be obtained by running Get-xDscOperation
By mentioning a sequence ID, the trace of the corresponding DSC operation is output.

.PARAMETER JobId
The event viewer shows each DSC event start with a unique job ID for each operation. If this job id is specified with this parameter, then all diagnostic messages displayed are taken from the dsc operation pertaining to this job id.

.PARAMETER ComputerName
The names of computers in which you would like to trace the past DSC operations

.PARAMETER Credential
The credential needed to access the computers specified inside ComputerName parameters

.EXAMPLE
Trace-xDscOperation
To Obtain the diagnostic information for the latest operation

.EXAMPLE
Trace-xDscOperation -sequenceId 3
To obtain the diagnostic information for the third latest operation

.EXAMPLE
Trace-xDscOperation -JobId 11112222-1111-1122-1122-111122221111
To diagnose an operation with job Id 11112222-1111-1122-1122-111122221111

.EXAMPLE
Trace-xDscOperation -ComputerName XYZ -sequenceID 2
To Get Logs from a remote computer

.EXAMPLE
Trace-xDscOperation -Computername XYZ -Credential $mycredential -sequenceID 2

To Get logs from a remote computer with credentials

.EXAMPLE
Trace-xDscOperation -ComputerName @("PN25113D0891", "PN25113D0890")

To get logs from multiple remote computers

.NOTES
Please note that to perform actions on the remote computer, have the firewall for remote configuration enabled. This can be done with the following command:

New-NetFirewallRule -Name "Service RemoteAdmin" -Action Allow
#>

function Trace-xDscOperation
{
    [cmdletBinding()]
    param
    (
        [UInt32]$SequenceID = 1, #latest is by default
        [Guid]$JobId,
        [String[]]$ComputerName,
        [pscredential]$Credential
    )

    Add-ClassTypes

    if ($ComputerName)
    {
        $script:UsingComputerName = $true
        $args = $PSBoundParameters
        $null = $args.Remove("ComputerName")
        $null = $args.Remove("Credential")

        foreach ($thisComputerName in $ComputerName)
        {
            LogDscDiagnostics -Verbose "Gathering logs for Computer $thisComputerName ..."
            $script:ThisComputerName = $thisComputerName
            $script:ThisCredential = $Credential
            Trace-DscOperationInternal  @PSBoundParameters
        }
    }
    else
    {
        $script:ThisComputerName = $env:COMPUTERNAME
        Trace-DscOperationInternal @PSBoundParameters
        $script:UsingComputerName = $false
    }
}
#EndRegion './Public/Trace-xDscOperation.ps1' 86
#Region './Public/Unprotect-xDscConfiguration.ps1' 0
# decrypt one of the lcm mof
function Unprotect-xDscConfiguration
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateSet('Current', 'Pending', 'Previous')]
        $Stage
    )

    Add-Type -AssemblyName System.Security

    $path = "$env:windir\System32\Configuration\$stage.mof"

    if (Test-Path $path)
    {
        $secureString = Get-Content $path -Raw

        $enc = [system.Text.Encoding]::Default

        $data = $enc.GetBytes($secureString)

        $bytes = [System.Security.Cryptography.ProtectedData]::Unprotect( $data, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine )

        $enc = [system.text.encoding]::Unicode

        $enc.GetString($bytes)
    }
    else
    {
        throw (New-Object -TypeName 'System.IO.FileNotFoundException' -ArgumentList @("The stage $stage was not found"))
    }
}
#EndRegion './Public/Unprotect-xDscConfiguration.ps1' 34
#Region './Public/Update-xDscEventLogStatus.ps1' 0
<#
.SYNOPSIS
Sets any DSC Event log (Operational, analytic, debug )

.DESCRIPTION
This cmdlet will set a DSC log when run with Update-xDscEventLogStatus <channel Name>.

.PARAMETER Channel
Mandatory parameter : Name of the channel of the event log to be set - It has to be one of Operational, Analytic or debug

.PARAMETER Status
Mandatory Parameter : This is a string parameter which is either "Enabled" or "disabled" representing the required final status of the log channel. If this value is "enabled", then the channel is enabled.

.PARAMETER ComputerName
String parameter that can be used to set the event log channel on a remote computer . Note : It may need a credential

.PARAMETER Credential
Credential to be passed in so that the operation can be performed on the remote computer

.EXAMPLE
C:\PS> Update-xDscEventLogStatus "Analytic" -Status "Enabled"

.EXAMPLE
C:\PS> Update-xDscEventLogStatus -Channel "Debug" -ComputerName "ABC"

.EXAMPLE
C:\PS> Update-xDscEventLogStatus -Channel "Debug" -ComputerName "ABC" -Status Disabled

#>

function Update-xDscEventLogStatus
{
    [cmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateSet('Analytic' , 'Debug' , 'Operational')]
        [String]$Channel,

        [Parameter(Mandatory)]
        [ValidateSet('Enabled' , 'Disabled')]
        [String]$Status,

        [String]$ComputerName,

        [PSCredential]$Credential
    )

    $LogName = "Microsoft-Windows-Dsc"
    $statusEnabled = $false
    $eventLogFullName = "$LogName/$Channel"
    if ($Status -eq "Enabled")
    {
        $statusEnabled = $true
    }
    #Form the basic command which will enable/disable any event log
    $commandToExecute = "wevtutil set-log $eventLogFullName /e:$statusEnabled /q:$statusEnabled "

    LogDscDiagnostics -Verbose "Changing status of the log $eventLogFullName to $Status"
    #If there is no computer name specified, just invoke the command in the same computer
    if (!$ComputerName)
    {
        Invoke-Expression $commandToExecute
    }
    else
    {
        #For any other computer, invoke command.
        $scriptToSetChannel = [Scriptblock]::Create($commandToExecute)

        if ($Credential)
        {
            Invoke-Command -ScriptBlock $scriptToSetChannel -ComputerName $ComputerName  -Credential $Credential
        }
        else
        {
            Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptToSetChannel
        }
    }

    LogDscDiagnostics -Verbose "The $Channel event log has been $Status. "
}
#EndRegion './Public/Update-xDscEventLogStatus.ps1' 80