Modules/SharePointDsc.Util/SharePointDsc.Util.psm1

function Add-SPDscEvent
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Message,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Source,

        [Parameter()]
        [ValidateSet('Error', 'Information', 'FailureAudit', 'SuccessAudit', 'Warning')]
        [System.String]
        $EntryType = 'Information',

        [Parameter()]
        [System.UInt32]
        $EventID = 1
    )

    $LogName = 'SPDsc'

    if ([System.Diagnostics.EventLog]::SourceExists($Source))
    {
        $sourceLogName = [System.Diagnostics.EventLog]::LogNameFromSourceName($Source, ".")
        if ($LogName -ne $sourceLogName)
        {
            Write-Verbose -Message "[ERROR] Specified source {$Source} already exists on log {$sourceLogName}"
            return
        }
    }
    else
    {
        if ([System.Diagnostics.EventLog]::Exists($LogName) -eq $false)
        {
            #Create event log
            $null = New-EventLog -LogName $LogName -Source $Source
        }
        else
        {
            [System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
        }
    }

    try
    {
        Write-EventLog -LogName $LogName -Source $Source `
            -EventId $EventID -Message $Message -EntryType $EntryType
    }
    catch
    {
        Write-Verbose -Message $_
    }
}

function Add-SPDscUserToLocalAdmin
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $UserName
    )

    if ($UserName.Contains("\") -eq $false)
    {
        $message = "Usernames should be formatted as domain\username"
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    $domainName = $UserName.Split('\')[0]
    $accountName = $UserName.Split('\')[1]

    Write-Verbose -Message "Adding $domainName\$userName to local admin group"
    ([ADSI]"WinNT://$($env:computername)/Administrators,group").Add("WinNT://$domainName/$accountName") | Out-Null
}

function Clear-SPDscKerberosToken
{
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Account
    )

    $sessions = klist.exe sessions
    foreach ($session in $sessions)
    {
        if ($session -like "*$($Account)*")
        {
            Write-Verbose -Message "Purging Kerberos ticket for $LogonId"
            $LogonId = $session.split(' ')[3]
            $LogonId = $LogonId.Replace('0:', '')
            klist.exe -li $LogonId purge | Out-Null
        }

    }
}

function Compare-PSCustomObjectArrays
{
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [System.Object[]]
        $DesiredValues,

        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [System.Object[]]
        $CurrentValues
    )

    $DriftedProperties = @()
    foreach ($DesiredEntry in $DesiredValues)
    {
        $Properties = $DesiredEntry.PSObject.Properties
        $KeyProperty = $Properties.Name[0]

        $EquivalentEntryInCurrent = $CurrentValues | Where-Object -FilterScript { $_.$KeyProperty -eq $DesiredEntry.$KeyProperty }
        if ($null -eq $EquivalentEntryInCurrent)
        {
            $result = @{
                Property     = $DesiredEntry
                PropertyName = $KeyProperty
                Desired      = $DesiredEntry.$KeyProperty
                Current      = $null
            }
            $DriftedProperties += $result
        }
        else
        {
            foreach ($property in $Properties)
            {
                $propertyName = $property.Name

                if ($DesiredEntry.$PropertyName -ne $EquivalentEntryInCurrent.$PropertyName)
                {
                    $result = @{
                        Property     = $DesiredEntry
                        PropertyName = $PropertyName
                        Desired      = $DesiredEntry.$PropertyName
                        Current      = $EquivalentEntryInCurrent.$PropertyName
                    }
                    $DriftedProperties += $result
                }
            }
        }
    }

    return $DriftedProperties
}

function Convert-SPDscADGroupIDToName
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Guid]
        $GroupId
    )

    $bytes = $GroupId.ToByteArray()
    $queryGuid = ""
    $bytes | ForEach-Object -Process {
        $queryGuid += "\" + $_.ToString("x2")
    }

    $domain = New-Object -TypeName "System.DirectoryServices.DirectoryEntry"
    $search = New-Object -TypeName "System.DirectoryServices.DirectorySearcher"
    $search.SearchRoot = $domain
    $search.PageSize = 1
    $search.Filter = "(&(objectGuid=$queryGuid))"
    $search.SearchScope = "Subtree"
    $search.PropertiesToLoad.Add("name") | Out-Null
    $result = $search.FindOne()

    if ($null -ne $result)
    {
        $sid = New-Object -TypeName "System.Security.Principal.SecurityIdentifier" `
            -ArgumentList @($result.GetDirectoryEntry().objectsid[0], 0)

        return $sid.Translate([System.Security.Principal.NTAccount]).ToString()
    }
    else
    {
        $message = "Unable to locate group with id $GroupId"
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }
}

function Convert-SPDscADGroupNameToID
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $GroupName
    )

    $groupNTaccount = New-Object -TypeName "System.Security.Principal.NTAccount" `
        -ArgumentList $groupName
    $groupSid = $groupNTaccount.Translate([System.Security.Principal.SecurityIdentifier])

    $result = New-Object -TypeName "System.DirectoryServices.DirectoryEntry" `
        -ArgumentList "LDAP://<SID=$($groupSid.ToString())>"
    return ([Guid]::new($result.objectGUID.Value))
}

function Convert-SPDscHashtableToString
{
    param
    (
        [Parameter()]
        [System.Collections.Hashtable]
        $Hashtable
    )
    $values = @()
    foreach ($pair in $Hashtable.GetEnumerator())
    {
        if ($pair.Value -is [System.Array])
        {
            $str = "$($pair.Key)=$(Convert-SPDscArrayToString -Array $pair.Value)"
        }
        elseif ($pair.Value -is [System.Collections.Hashtable])
        {
            $str = "$($pair.Key)={$(Convert-SPDscHashtableToString -Hashtable $pair.Value)}"
        }
        elseif ($pair.Value -is [Microsoft.Management.Infrastructure.CimInstance])
        {
            $str = "$($pair.Key)=$(Convert-SPDscCIMInstanceToString -CIMInstance $pair.Value)"
        }
        elseif ($pair.Value -is [System.Management.Automation.PSCredential])
        {
            $str = "$($pair.Key)=$($pair.Value.UserName)"
        }
        else
        {
            $str = "$($pair.Key)=$($pair.Value)"
        }
        $values += $str
    }

    [array]::Sort($values)
    return ($values -join "; ")
}

function Convert-SPDscArrayToString
{
    param
    (
        [Parameter()]
        [System.Array]
        $Array
    )

    $str = "("
    for ($i = 0; $i -lt $Array.Count; $i++)
    {
        $item = $Array[$i]
        if ($item -is [System.Collections.Hashtable])
        {
            $str += "{"
            $str += Convert-SPDscHashtableToString -Hashtable $item
            $str += "}"
        }
        elseif ($Array[$i] -is [Microsoft.Management.Infrastructure.CimInstance])
        {
            $str += Convert-SPDscCIMInstanceToString -CIMInstance $item
        }
        else
        {
            $str += $item
        }

        if ($i -lt ($Array.Count - 1))
        {
            $str += ","
        }
    }
    $str += ")"

    return $str
}

function Convert-SPDscCIMInstanceToString
{
    param
    (
        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance]
        $CIMInstance
    )

    $str = "{"
    foreach ($prop in $CIMInstance.CimInstanceProperties)
    {
        if ($str -notmatch "{$")
        {
            $str += "; "
        }
        $str += "$($prop.Name)=$($prop.Value)"
    }
    $str += "}"

    return $str
}

function Get-SPDscOSVersion
{
    [CmdletBinding()]
    param ()
    return [System.Environment]::OSVersion.Version
}

function Get-SPDscAssemblyVersion
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $PathToAssembly
    )
    return (Get-Command $PathToAssembly).FileVersionInfo.FileMajorPart
}

function Get-SPDscBuildVersion
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $PathToAssembly
    )
    return (Get-Command $PathToAssembly).FileVersionInfo.FileBuildPart
}

function Get-SPDscFarmAccount
{
    [CmdletBinding()]
    param
    ()

    $farmaccount = (Get-SPFarm).DefaultServiceAccount.Name

    $account = Get-SPManagedAccount | Where-Object -FilterScript { $_.UserName -eq $farmaccount }

    $bindings = [System.Reflection.BindingFlags]::CreateInstance -bor `
        [System.Reflection.BindingFlags]::GetField -bor `
        [System.Reflection.BindingFlags]::Instance -bor `
        [System.Reflection.BindingFlags]::NonPublic

    $pw = $account.GetType().GetField("m_Password", $bindings).GetValue($account);

    return New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $farmaccount, $pw.SecureStringValue
}


function Get-SPDscFarmAccountName
{
    [CmdletBinding()]
    param
    ()
    $spFarm = Get-SPFarm
    return $spFarm.DefaultServiceAccount.Name
}

function Get-SPDscFarmVersionInfo
{
    param
    (
        [Parameter()]
        [System.String]
        $ProductToCheck
    )

    $farm = Get-SPFarm
    $productVersions = [Microsoft.SharePoint.Administration.SPProductVersions]::GetProductVersions($farm)
    $server = Get-SPServer -Identity $env:COMPUTERNAME
    $versionInfo = @{ }
    $versionInfo.Highest = ""
    $versionInfo.Lowest = ""

    $serverProductInfo = $productVersions.GetServerProductInfo($server.id)
    $products = $serverProductInfo.Products

    if ($ProductToCheck)
    {
        $products = $products | Where-Object -FilterScript {
            $_ -eq $ProductToCheck
        }

        if ($null -eq $products)
        {
            $message = "Product not found: $ProductToCheck"
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $MyInvocation.MyCommand.Source
            throw $message
        }
    }

    # Loop through all products
    foreach ($product in $products)
    {
        $singleProductInfo = $serverProductInfo.GetSingleProductInfo($product)
        $patchableUnits = $singleProductInfo.PatchableUnitDisplayNames

        # Loop through all individual components within the product
        foreach ($patchableUnit in $patchableUnits)
        {
            # Check if the displayname is the Proofing tools (always mentioned in first product,
            # generates noise)
            if (($patchableUnit -notmatch "Microsoft Server Proof") -and
                ($patchableUnit -notmatch "SQL Express") -and
                ($patchableUnit -notmatch "OMUI") -and
                ($patchableUnit -notmatch "XMUI") -and
                ($patchableUnit -notmatch "Project Server") -and
                (($patchableUnit -notmatch "Microsoft SharePoint Server (2013|2016|2019)" -or `
                        $patchableUnit -match "Core")))
            {
                $patchableUnitsInfo = $singleProductInfo.GetPatchableUnitInfoByDisplayName($patchableUnit)
                $currentVersion = ""
                foreach ($patchableUnitInfo in $patchableUnitsInfo)
                {
                    # Loop through version of the patchableUnit
                    $currentVersion = $patchableUnitInfo.LatestPatch.Version.ToString()

                    # Check if the version of the patchableUnit is the highest for the installed product
                    if ($currentVersion -gt $versionInfo.Highest)
                    {
                        $versionInfo.Highest = $currentVersion
                    }

                    if ($versionInfo.Lowest -eq "")
                    {
                        $versionInfo.Lowest = $currentVersion
                    }
                    else
                    {
                        if ($currentversion -lt $versionInfo.Lowest)
                        {
                            $versionInfo.Lowest = $currentVersion
                        }
                    }
                }
            }
        }
    }
    return $versionInfo
}

function Get-SPDscFarmProductsInfo
{
    $farm = Get-SPFarm
    $productVersions = [Microsoft.SharePoint.Administration.SPProductVersions]::GetProductVersions($farm)
    $server = Get-SPServer -Identity $env:COMPUTERNAME

    $serverProductInfo = $productVersions.GetServerProductInfo($server.id)
    return $serverProductInfo.Products
}

function Get-SPDscRegProductsInfo
{
    $registryLocation = Get-ChildItem -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall"
    $sharePointPrograms = $registryLocation | Where-Object -FilterScript {
        $_.PsPath -like "*\Office*"
    } | ForEach-Object -Process {
        Get-ItemProperty -Path $_.PsPath
    }

    return $sharePointPrograms.DisplayName
}

function Get-SPDscRegistryKey
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Key,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Value
    )

    if ((Test-Path -Path $Key) -eq $true)
    {
        $regKey = Get-ItemProperty -LiteralPath $Key
        return $regKey.$Value
    }
    else
    {
        $message = "Specified registry key $Key could not be found."
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }
}

function Get-SPDscServerPatchStatus
{
    $farm = Get-SPFarm
    $productVersions = [Microsoft.SharePoint.Administration.SPProductVersions]::GetProductVersions($farm)
    $server = Get-SPServer $env:COMPUTERNAME
    $serverProductInfo = $productVersions.GetServerProductInfo($server.Id);
    if ($null -ne $serverProductInfo)
    {
        $statusType = $serverProductInfo.InstallStatus;
        if ($statusType -ne 0)
        {
            $statusType = $serverProductInfo.GetUpgradeStatus($farm, $server);
        }
    }
    else
    {
        $statusType = [Microsoft.SharePoint.Administration.SPServerProductInfo+StatusType]::NoActionRequired;
    }

    return $statusType
}

function Get-SPDscServiceContext
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        $ProxyGroup
    )
    Write-Verbose -Message "Getting SPContext for Proxy group $($proxyGroup)"
    return [Microsoft.SharePoint.SPServiceContext]::GetContext($proxyGroup, [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
}

function Get-SPDscContentService
{
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null
    return [Microsoft.SharePoint.Administration.SPWebService]::ContentService
}

function Get-SPDscUserProfileSubTypeManager
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        $Context
    )
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null
    return [Microsoft.Office.Server.UserProfiles.ProfileSubtypeManager]::Get($Context)
}

function Get-SPDscInstalledProductVersion
{
    [OutputType([System.Version])]
    param ()

    $pathToSearch = 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\*\ISAPI\Microsoft.SharePoint.dll'
    $fullPath = Get-Item $pathToSearch -ErrorAction SilentlyContinue | Sort-Object { $_.Directory } -Descending | Select-Object -First 1
    if ($null -eq $fullPath)
    {
        $message = 'SharePoint path {C:\Program Files\Common Files\microsoft shared\Web Server Extensions} does not exist'
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }
    else
    {
        return (Get-Command $fullPath).FileVersionInfo
    }
}

function Invoke-SPDscCommand
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [Object[]]
        $Arguments,

        [Parameter(Mandatory = $true)]
        [ScriptBlock]
        $ScriptBlock
    )

    $VerbosePreference = 'Continue'

    $installedVersion = Get-SPDscInstalledProductVersion
    if ($installedVersion.ProductMajorPart -eq 15 -or $installedVersion.ProductBuildPart -le 12999)
    {
        $baseScript = @"
            if (`$null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue))
            {
                Add-PSSnapin Microsoft.SharePoint.PowerShell
            }
 
"@

    }
    else
    {
        $baseScript = @"
            Import-Module SharePointServer -Verbose:`$false -WarningAction SilentlyContinue
 
"@

    }

    $invokeArgs = @{
        ScriptBlock = [ScriptBlock]::Create($baseScript + $ScriptBlock.ToString())
    }
    if ($null -ne $Arguments)
    {
        $invokeArgs.Add("ArgumentList", $Arguments)
    }

    if ($null -eq $Credential)
    {
        if ($Env:USERNAME.Contains("$"))
        {
            $message = ("You need to specify a value for either InstallAccount " + `
                    "or PsDscRunAsCredential.")
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $MyInvocation.MyCommand.Source
            throw $message
        }
        Write-Verbose -Message "Executing as the local run as user $($Env:USERDOMAIN)\$($Env:USERNAME)"

        try
        {
            return Invoke-Command @invokeArgs -Verbose
        }
        catch
        {
            if ($_.Exception.Message.Contains("An update conflict has occurred, and you must re-try this action"))
            {
                Write-Verbose -Message ("Detected an update conflict, restarting server to " + `
                        "allow DSC to resume and retry")
                $global:DSCMachineStatus = 1
            }
            else
            {
                $message = $_
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $MyInvocation.MyCommand.Source
                throw $message
            }
        }
    }
    else
    {
        if ($Credential.UserName.Split("\")[1] -eq $Env:USERNAME)
        {
            if (-not $Env:USERNAME.Contains("$"))
            {
                $message = ("Unable to use both InstallAccount and " + `
                        "PsDscRunAsCredential in a single resource. Remove one " + `
                        "and try again.")
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $MyInvocation.MyCommand.Source
                throw $message
            }
        }
        Write-Verbose -Message ("Executing using a provided credential and local PSSession " + `
                "as user $($Credential.UserName)")

        # Running garbage collection to resolve issues related to Azure DSC extention use
        [GC]::Collect()

        $session = New-PSSession -ComputerName $env:COMPUTERNAME `
            -Credential $Credential `
            -Authentication CredSSP `
            -Name "Microsoft.SharePoint.DSC" `
            -SessionOption (New-PSSessionOption -OperationTimeout 0 `
                -IdleTimeout 60000) `
            -ErrorAction Continue

        if ($session)
        {
            $invokeArgs.Add("Session", $session)
        }

        try
        {
            return Invoke-Command @invokeArgs -Verbose
        }
        catch
        {
            if ($_.Exception.Message.Contains("An update conflict has occurred, and you must re-try this action"))
            {
                Write-Verbose -Message ("Detected an update conflict, restarting server to " + `
                        "allow DSC to resume and retry")
                $global:DSCMachineStatus = 1
            }
            else
            {
                $message = $_
                Add-SPDscEvent -Message $message `
                    -EntryType 'Error' `
                    -EventID 100 `
                    -Source $MyInvocation.MyCommand.Source
                throw $message
            }
        }
        finally
        {
            if ($session)
            {
                Remove-PSSession -Session $session
            }
        }
    }
}

function Rename-SPDscParamValue
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1, ValueFromPipeline = $true)]
        $Params,

        [Parameter(Mandatory = $true, Position = 2)]
        $OldName,

        [Parameter(Mandatory = $true, Position = 3)]
        $NewName
    )

    if ($Params.ContainsKey($OldName))
    {
        $Params.Add($NewName, $Params.$OldName)
        $Params.Remove($OldName) | Out-Null
    }
    return $Params
}

function Remove-SPDscUserToLocalAdmin
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $UserName
    )

    if ($UserName.Contains("\") -eq $false)
    {
        $message = "Usernames should be formatted as domain\username"
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    $domainName = $UserName.Split('\')[0]
    $accountName = $UserName.Split('\')[1]

    Write-Verbose -Message "Removing $domainName\$userName from local admin group"
    ([ADSI]"WinNT://$($env:computername)/Administrators,group").Remove("WinNT://$domainName/$accountName") | Out-Null
}

function Remove-SPDscZoneMap
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ServerName
    )

    $zoneMap = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap"

    $escDomainsPath = Join-Path -Path $zoneMap -ChildPath "\EscDomains\$ServerName"
    if (Test-Path -Path $escDomainsPath)
    {
        Remove-Item -Path $escDomainsPath
    }

    $domainsPath = Join-Path -Path $zoneMap -ChildPath "\Domains\$ServerName"
    if (Test-Path -Path $domainsPath)
    {
        Remove-Item -Path $domainsPath
    }
}

function Resolve-SPDscSecurityIdentifier
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $SID
    )
    $memberName = ([wmi]"Win32_SID.SID='$SID'").AccountName
    $memberName = "$($env:USERDOMAIN)\$memberName"
    return $memberName
}

function Set-SPDscZoneMap
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $ServerName
    )

    $zoneMap = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap"

    $escDomainsPath = Join-Path -Path $zoneMap -ChildPath "\EscDomains\$ServerName"
    if (-not (Test-Path -Path $escDomainsPath))
    {
        $null = New-Item -Path $escDomainsPath -Force
    }

    if ((Get-ItemProperty -Path $escDomainsPath).File -ne 1)
    {
        Set-ItemProperty -Path $escDomainsPath -Name file -Value 1 -Type DWord
    }

    $domainsPath = Join-Path -Path $zoneMap -ChildPath "\Domains\$ServerName"
    if (-not (Test-Path -Path $domainsPath))
    {
        $null = New-Item -Path $domainsPath -Force
    }

    if ((Get-ItemProperty -Path $domainsPath).File -ne 1)
    {
        Set-ItemProperty -Path $domainsPath -Name file -Value 1 -Type DWord
    }
}

function Test-SPDscObjectHasProperty
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [Object]
        $Object,

        [Parameter(Mandatory = $true, Position = 2)]
        [String]
        $PropertyName
    )

    if (([bool]($Object.PSobject.Properties.name -contains $PropertyName)) -eq $true)
    {
        if ($null -ne $Object.$PropertyName)
        {
            return $true
        }
    }
    return $false
}

function Test-SPDscRunningAsFarmAccount
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $InstallAccount
    )

    if ($null -eq $InstallAccount)
    {
        if ($Env:USERNAME.Contains("$"))
        {
            $message = "You need to specify a value for either InstallAccount or PsDscRunAsCredential."
            Add-SPDscEvent -Message $message `
                -EntryType 'Error' `
                -EventID 100 `
                -Source $MyInvocation.MyCommand.Source
            throw $message
        }
        $Username = "$($Env:USERDOMAIN)\$($Env:USERNAME)"
    }
    else
    {
        $Username = $InstallAccount.UserName
    }

    $result = Invoke-SPDscCommand -Credential $InstallAccount -ScriptBlock {
        try
        {
            $spFarm = Get-SPFarm
        }
        catch
        {
            Write-Verbose -Message "Unable to detect local farm."
            return $null
        }
        return $spFarm.DefaultServiceAccount.Name
    }

    if ($Username -eq $result)
    {
        return $true
    }
    return $false
}

function Test-SPDscParameterState
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [HashTable]
        $CurrentValues,

        [Parameter(Mandatory = $true, Position = 2)]
        [Object]
        $DesiredValues,

        [Parameter(Position = 3)]
        [Array]
        $ValuesToCheck,

        [Parameter(Position = 4)]
        [System.String]
        $Source = 'Generic'
    )

    $returnValue = $true

    $DriftedParameters = @{ }

    if (($DesiredValues.GetType().Name -ne "HashTable") -and `
        ($DesiredValues.GetType().Name -ne "CimInstance") -and `
        ($DesiredValues.GetType().Name -ne "PSBoundParametersDictionary"))
    {
        $message = ("Property 'DesiredValues' in Test-SPDscParameterState must be either a " + `
                "Hashtable or CimInstance. Type detected was $($DesiredValues.GetType().Name)")
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    if (($DesiredValues.GetType().Name -eq "CimInstance") -and ($null -eq $ValuesToCheck))
    {
        $message = ("If 'DesiredValues' is a CimInstance then property 'ValuesToCheck' must contain " + `
                "a value")
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1))
    {
        $KeyList = $DesiredValues.Keys
    }
    else
    {
        $KeyList = $ValuesToCheck
    }

    $KeyList | ForEach-Object -Process {
        if (($_ -ne "Verbose") -and ($_ -ne "InstallAccount"))
        {
            if (($CurrentValues.ContainsKey($_) -eq $false) -or `
                ($CurrentValues.$_ -ne $DesiredValues.$_) -or `
                (($DesiredValues.ContainsKey($_) -eq $true) -and `
                    ($null -ne $DesiredValues.$_ -and `
                            $DesiredValues.$_.GetType().IsArray)))
            {
                if ($DesiredValues.GetType().Name -eq "HashTable" -or `
                        $DesiredValues.GetType().Name -eq "PSBoundParametersDictionary")
                {
                    $CheckDesiredValue = $DesiredValues.ContainsKey($_)
                }
                else
                {
                    $CheckDesiredValue = Test-SPDscObjectHasProperty -Object $DesiredValues -PropertyName $_
                }

                if ($CheckDesiredValue)
                {
                    $desiredType = $DesiredValues.$_.GetType()
                    $fieldName = $_
                    if ($desiredType.IsArray -eq $true)
                    {
                        if (($CurrentValues.ContainsKey($fieldName) -eq $false) -or `
                            ($null -eq $CurrentValues.$fieldName))
                        {
                            Write-Verbose -Message ("Expected to find an array value for " + `
                                    "property $fieldName in the current " + `
                                    "values, but it was either not present or " + `
                                    "was null. This has caused the test method " + `
                                    "to return false.")
                            $DriftedParameters.Add($fieldName, '')
                            $returnValue = $false
                        }
                        elseif ($desiredType.Name -eq 'ciminstance[]')
                        {
                            Write-Verbose "The current property {$_} is a CimInstance[]"
                            $AllDesiredValuesAsArray = @()
                            foreach ($item in $DesiredValues.$_)
                            {
                                $currentEntry = @{ }
                                foreach ($prop in $item.CIMInstanceProperties)
                                {
                                    $value = $prop.Value
                                    if ([System.String]::IsNullOrEmpty($value))
                                    {
                                        $value = $null
                                    }
                                    $currentEntry.Add($prop.Name, $value)
                                }
                                $AllDesiredValuesAsArray += [PSCustomObject]$currentEntry
                            }

                            $arrayCompare = Compare-PSCustomObjectArrays -CurrentValues $CurrentValues.$fieldName `
                                -DesiredValues $AllDesiredValuesAsArray
                            if ($null -ne $arrayCompare)
                            {
                                foreach ($item in $arrayCompare)
                                {
                                    $EventValue = "<CurrentValue>[$($item.PropertyName)]$($item.Current)</CurrentValue>"
                                    $EventValue += "<DesiredValue>[$($item.PropertyName)]$($item.Desired)</DesiredValue>"
                                    if (-not $DriftedParameters.ContainsKey($fieldName))
                                    {
                                        $DriftedParameters.Add($fieldName, @())
                                    }
                                    $DriftedParameters[$fieldName] = $DriftedParameters[$fieldName] += $EventValue
                                }
                                $returnValue = $false
                            }
                        }
                        else
                        {
                            $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName `
                                -DifferenceObject $DesiredValues.$fieldName
                            if ($null -ne $arrayCompare -and `
                                    -not [System.String]::IsNullOrEmpty($arrayCompare.InputObject))
                            {
                                Write-Verbose -Message ("Found an array for property $fieldName " + `
                                        "in the current values, but this array " + `
                                        "does not match the desired state. " + `
                                        "Details of the changes are below.")
                                $arrayCompare | ForEach-Object -Process {
                                    Write-Verbose -Message "$($_.InputObject) - $($_.SideIndicator)"
                                }

                                $EventValue = "<CurrentValue>$($CurrentValues.$fieldName -join ", ")</CurrentValue>"
                                $EventValue += "<DesiredValue>$($DesiredValues.$fieldName -join ", ")</DesiredValue>"
                                $DriftedParameters.Add($fieldName, $EventValue)
                                $returnValue = $false
                            }
                        }
                    }
                    else
                    {
                        switch ($desiredType.Name)
                        {
                            "String"
                            {
                                if ([string]::IsNullOrEmpty($CurrentValues.$fieldName) -and `
                                        [string]::IsNullOrEmpty($DesiredValues.$fieldName))
                                {
                                }
                                else
                                {
                                    Write-Verbose -Message ("String value for property " + `
                                            "$fieldName does not match. " + `
                                            "Current state is " + `
                                            "'$($CurrentValues.$fieldName)' " + `
                                            "and desired state is " + `
                                            "'$($DesiredValues.$fieldName)'")
                                    $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                    $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                    $DriftedParameters.Add($fieldName, $EventValue)
                                    $returnValue = $false
                                }
                            }
                            "Int32"
                            {
                                if (($DesiredValues.$fieldName -eq 0) -and `
                                    ($null -eq $CurrentValues.$fieldName))
                                {
                                }
                                else
                                {
                                    Write-Verbose -Message ("Int32 value for property " + `
                                            "$fieldName does not match. " + `
                                            "Current state is " + `
                                            "'$($CurrentValues.$fieldName)' " + `
                                            "and desired state is " + `
                                            "'$($DesiredValues.$fieldName)'")
                                    $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                    $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                    $DriftedParameters.Add($fieldName, $EventValue)
                                    $returnValue = $false
                                }
                            }
                            "Int16"
                            {
                                if (($DesiredValues.$fieldName -eq 0) -and `
                                    ($null -eq $CurrentValues.$fieldName))
                                {
                                }
                                else
                                {
                                    Write-Verbose -Message ("Int16 value for property " + `
                                            "$fieldName does not match. " + `
                                            "Current state is " + `
                                            "'$($CurrentValues.$fieldName)' " + `
                                            "and desired state is " + `
                                            "'$($DesiredValues.$fieldName)'")
                                    $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                    $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                    $DriftedParameters.Add($fieldName, $EventValue)
                                    $returnValue = $false
                                }
                            }
                            "Boolean"
                            {
                                if ($CurrentValues.$fieldName -ne $DesiredValues.$fieldName)
                                {
                                    Write-Verbose -Message ("Boolean value for property " + `
                                            "$fieldName does not match. " + `
                                            "Current state is " + `
                                            "'$($CurrentValues.$fieldName)' " + `
                                            "and desired state is " + `
                                            "'$($DesiredValues.$fieldName)'")
                                    $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                    $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                    $DriftedParameters.Add($fieldName, $EventValue)
                                    $returnValue = $false
                                }
                            }
                            "Single"
                            {
                                if (($DesiredValues.$fieldName -eq 0) -and `
                                    ($null -eq $CurrentValues.$fieldName))
                                {
                                }
                                else
                                {
                                    Write-Verbose -Message ("Single value for property " + `
                                            "$fieldName does not match. " + `
                                            "Current state is " + `
                                            "'$($CurrentValues.$fieldName)' " + `
                                            "and desired state is " + `
                                            "'$($DesiredValues.$fieldName)'")
                                    $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                    $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                    $DriftedParameters.Add($fieldName, $EventValue)
                                    $returnValue = $false
                                }
                            }
                            "Hashtable"
                            {
                                Write-Verbose -Message "The current property {$fieldName} is a Hashtable"
                                $AllDesiredValuesAsArray = @()
                                foreach ($item in $DesiredValues.$fieldName)
                                {
                                    $currentEntry = @{ }
                                    foreach ($key in $item.Keys)
                                    {
                                        $value = $item.$key
                                        if ([System.String]::IsNullOrEmpty($value))
                                        {
                                            $value = $null
                                        }
                                        $currentEntry.Add($key, $value)
                                    }
                                    $AllDesiredValuesAsArray += [PSCustomObject]$currentEntry
                                }

                                if ($null -ne $DesiredValues.$fieldName -and $null -eq $CurrentValues.$fieldName)
                                {
                                    $returnValue = $false
                                }
                                else
                                {
                                    $AllCurrentValuesAsArray = @()
                                    foreach ($item in $CurrentValues.$fieldName)
                                    {
                                        $currentEntry = @{ }
                                        foreach ($key in $item.Keys)
                                        {
                                            $value = $item.$key
                                            if ([System.String]::IsNullOrEmpty($value))
                                            {
                                                $value = $null
                                            }
                                            $currentEntry.Add($key, $value)
                                        }
                                        $AllCurrentValuesAsArray += [PSCustomObject]$currentEntry
                                    }
                                    $arrayCompare = Compare-PSCustomObjectArrays -CurrentValues $AllCurrentValuesAsArray `
                                        -DesiredValues $AllCurrentValuesAsArray
                                    if ($null -ne $arrayCompare)
                                    {
                                        foreach ($item in $arrayCompare)
                                        {
                                            $EventValue = "<CurrentValue>[$($item.PropertyName)]$($item.Current)</CurrentValue>"
                                            $EventValue += "<DesiredValue>[$($item.PropertyName)]$($item.Desired)</DesiredValue>"
                                            if (-not $DriftedParameters.ContainsKey($fieldName))
                                            {
                                                $DriftedParameters.Add($fieldName, @())
                                            }
                                            $DriftedParameters[$fieldName] = $DriftedParameters[$fieldName] += $EventValue
                                        }
                                        $returnValue = $false
                                    }
                                }
                            }
                            default
                            {
                                Write-Verbose -Message ("Unable to compare property $fieldName " + `
                                        "as the type ($($desiredType.Name)) is " + `
                                        "not handled by the " + `
                                        "Test-SPDscParameterState cmdlet")
                                $EventValue = "<CurrentValue>$($CurrentValues.$fieldName)</CurrentValue>"
                                $EventValue += "<DesiredValue>$($DesiredValues.$fieldName)</DesiredValue>"
                                $DriftedParameters.Add($fieldName, $EventValue)
                                $returnValue = $false
                            }
                        }
                    }
                }
            }
        }
    }

    if ($returnValue -eq $false)
    {
        $EventMessage = "<SPDscEvent>`r`n"
        $EventMessage += " <ConfigurationDrift Source=`"$Source`">`r`n"

        $EventMessage += " <ParametersNotInDesiredState>`r`n"
        foreach ($key in $DriftedParameters.Keys)
        {
            Write-Verbose -Message "Detected Drifted Parameter [$Source]$key"
            $EventMessage += " <Param Name=`"$key`">" + $DriftedParameters.$key + "</Param>`r`n"
        }
        $EventMessage += " </ParametersNotInDesiredState>`r`n"
        $EventMessage += " </ConfigurationDrift>`r`n"
        $EventMessage += " <DesiredValues>`r`n"
        foreach ($Key in $DesiredValues.Keys)
        {
            $Value = $DesiredValues.$Key -join ", "
            if ([System.String]::IsNullOrEmpty($Value))
            {
                $Value = "`$null"
            }
            $EventMessage += " <Param Name =`"$key`">$Value</Param>`r`n"
        }
        $EventMessage += " </DesiredValues>`r`n"
        $EventMessage += "</SPDscEvent>"

        Add-SPDscEvent -Message $EventMessage -EntryType 'Error' -EventID 1 -Source $Source
    }

    return $returnValue
}

function Test-SPDscUserIsLocalAdmin
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $UserName
    )

    if ($UserName.Contains("\") -eq $false)
    {
        $message = "Usernames should be formatted as domain\username"
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    $accountName = $UserName.Split('\')[1]

    return ([ADSI]"WinNT://$($env:computername)/Administrators,group").PSBase.Invoke("Members") | `
            ForEach-Object -Process {
            $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
        } | Where-Object -FilterScript {
            $_ -eq $accountName
        }
}

function Test-SPDscIsADUser
{
    [OutputType([System.Boolean])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $IdentityName
    )

    $DomainNetbiosName = ""

    if ($IdentityName -like "*\*")
    {
        $DomainNetbiosName = $IdentityName.Split('\')[0]
        $IdentityName = $IdentityName.Substring($IdentityName.IndexOf('\') + 1)
    }

    $domainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain", $DomainNetbiosName)
    try
    {
        $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($domainContext)
        $root = $domain.GetDirectoryEntry()

        $searcher = [System.DirectoryServices.DirectorySearcher]::new()
        $searcher.filter = "((samAccountName=$IdentityName))"
        $searcher.SearchScope = "subtree"
        $searcher.SearchRoot = $root

        $searcher.PropertiesToLoad.Add("objectClass") | Out-Null
        $searcher.PropertiesToLoad.Add("objectCategory") | Out-Null
        $searcher.PropertiesToLoad.Add("name") | Out-Null
        $result = $searcher.FindOne()
    }
    catch
    {
        return $false
    }

    if ($null -eq $result)
    {
        Write-Host "Unable to locate identity '$IdentityName' in the current domain."
        return $false
    }

    if ($result[0].Properties.objectclass -contains "user")
    {
        return $true
    }
    else
    {
        return $false
    }
}

function Set-SPDscObjectPropertyIfValuePresent
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [object]
        $ObjectToSet,

        [Parameter(Mandatory = $true)]
        [string]
        $PropertyToSet,

        [Parameter(Mandatory = $true)]
        [object]
        $ParamsValue,

        [Parameter(Mandatory = $true)]
        [string]
        $ParamKey
    )
    if ($ParamsValue.PSobject.Methods.name -contains "ContainsKey")
    {
        if ($ParamsValue.ContainsKey($ParamKey) -eq $true)
        {
            $ObjectToSet.$PropertyToSet = $ParamsValue.$ParamKey
        }
    }
    else
    {
        if (((Test-SPDscObjectHasProperty $ParamsValue $ParamKey) -eq $true) `
                -and ($null -ne $ParamsValue.$ParamKey))
        {
            $ObjectToSet.$PropertyToSet = $ParamsValue.$ParamKey
        }
    }
}

function Remove-SPDscGenericObject
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Object]
        $SourceCollection,

        [Parameter(Mandatory = $true)]
        [Object]
        $Target
    )
    $SourceCollection.Remove($Target)
}

function Format-OfficePatchGUID
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $PatchGUID
    )

    $guidParts = $PatchGUID.Split("-")
    if ($guidParts.Count -ne 5 `
            -or $guidParts[0].Length -ne 8 `
            -or $guidParts[1].Length -ne 4 `
            -or $guidParts[2].Length -ne 4 `
            -or $guidParts[3].Length -ne 4 `
            -or $guidParts[4].Length -ne 12)
    {
        $message = "The provided Office Patch GUID is not in the expected format (e.g. XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    $newPart1 = ConvertTo-ReverseString -InputString $guidParts[0]
    $newPart2 = ConvertTo-ReverseString -InputString $guidParts[1]
    $newPart3 = ConvertTo-ReverseString -InputString $guidParts[2]
    $newPart4 = ConvertTo-TwoDigitFlipString -InputString $guidParts[3]
    $newPart5 = ConvertTo-TwoDigitFlipString -InputString $guidParts[4]

    $newGUID = $newPart1 + $newPart2 + $newPart3 + $newPart4 + $newPart5
    return $newGUID
}

function ConvertTo-TwoDigitFlipString
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InputString
    )

    if ($InputString.Length % 2 -ne 0)
    {
        $message = "The input string was not in the correct format. It needs to have an even length."
        Add-SPDscEvent -Message $message `
            -EntryType 'Error' `
            -EventID 100 `
            -Source $MyInvocation.MyCommand.Source
        throw $message
    }

    $flippedString = ""

    for ($i = 0; $i -lt $InputString.Length; $i++)
    {
        $flippedString += $InputString[$i + 1] + $InputString[$i]
        $i++
    }
    return $flippedString
}

function ConvertTo-ReverseString
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $InputString
    )

    $reverseString = ""
    for ($i = $InputString.Length - 1; $i -ge 0; $i--)
    {
        $reverseString += $InputString[$i]
    }
    return $reverseString
}

function Export-SPConfiguration
{
    param
    (
        [Parameter()]
        [switch]
        $Quiet = $false,

        [Parameter()]
        [ValidateSet("Lite", "Default", "Full")]
        [System.String]
        $Mode = "Default",

        [Parameter()]
        [switch]
        $Standalone,

        [Parameter()]
        [String]
        $OutputFile = $null,

        [Parameter()]
        [String]
        $OutputPath = $null,

        [Parameter()]
        [switch]
        $SkipSitesAndWebs = $false,

        [Parameter()]
        [switch]
        $Azure = $false,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credentials,

        [Parameter()]
        [System.Object[]]
        $ComponentsToExtract,

        [Parameter()]
        [switch]
        $DynamicCompilation,

        [Parameter()]
        [String]
        $ProductKey,

        [Parameter()]
        [String]
        $BinaryLocation
    )

    $reverseDSCVersion = [Version]"2.0.0.7"
    $reverseDSCModule = Get-Module ReverseDsc -ListAvailable | Where-Object -FilterScript { $_.Version -eq $reverseDSCVersion }
    if ($null -eq $reverseDSCModule)
    {
        Write-Host "[ERROR} ReverseDsc v$($reverseDSCVersion.ToString()) could not be found. Make sure you have this module installed before running this cmdlet!" -ForegroundColor Red
        Write-Host " "
        Write-Host "Install via:" -ForegroundColor Red
        Write-Host " Install-Module ReverseDsc -RequiredVersion $($reverseDSCVersion.ToString())" -ForegroundColor Red
        Write-Host "or" -ForegroundColor Red
        Write-Host " Copy the module from a different machine to C:\Program Files\WindowsPowerShell\Modules" -ForegroundColor Red
        Write-Host " "
        return
    }

    $spDscModule = (Get-Module "SharePointDSC")
    $spDscModulePath = Split-Path -Path $spDscModule.Path -Parent
    Import-Module -Name (Join-Path -Path $spDscModulePath -ChildPath "Modules\SharePointDSC.Reverse\SharePointDSC.Reverse.psm1") -Scope Global

    <## Script Settings #>
    $VerbosePreference = "SilentlyContinue"

    <## Scripts Variables #>
    $Script:DH_SPQUOTATEMPLATE = @{}
    $Script:dscConfigContent = ""
    $Global:CredsRepo = @()
    $Global:AllUsers = @()
    $Script:ErrorLog = ""
    $Script:configName = ""
    $Script:currentServerName = ""
    $SPDSCSource = "$env:ProgramFiles\WindowsPowerShell\Modules\SharePointDSC\"
    $Script:spCentralAdmin = ""
    $Script:ExtractionModeValue = "2"
    $script:SkipSitesAndWebs = $SkipSitesAndWebs

    if ($Quiet)
    {
        Write-Warning "-Quiet is deprecated. For unattended extraction, please use the -ComponentsToExtract parameter."
    }

    if ($Mode.ToLower() -eq "lite")
    {
        $Script:ExtractionModeValue = 1
    }
    elseif ($Mode.ToLower() -eq "full")
    {
        $Script:ExtractionModeValue = 3
    }

    $Script:version = $spDscModule.Version.ToString()
    $Global:spFarmAccount = ""

    $sharePointSnapin = Get-PSSnapin | Where-Object { $_.Name -eq "Microsoft.SharePoint.PowerShell" }
    if ($null -ne $sharePointSnapin)
    {
        if ($Quiet -or $ComponentsToExtract.Count -gt 0)
        {
            if ($StandAlone)
            {
                if ($DynamicCompilation)
                {
                    Get-SPReverseDSC -ComponentsToExtract $ComponentsToExtract -Credentials $Credentials -OutputPath $OutputPath -StandAlone -DynamicCompilation -ProductKey $ProductKey -BinaryLocation $BinaryLocation
                }
                else
                {
                    Get-SPReverseDSC -ComponentsToExtract $ComponentsToExtract -Credentials $Credentials -OutputPath $OutputPath -StandAlone -ProductKey $ProductKey -BinaryLocation $BinaryLocation
                }
            }
            else
            {
                Get-SPReverseDSC -ComponentsToExtract $ComponentsToExtract -Credentials $Credentials -OutputPath $OutputPath -ProductKey $ProductKey -BinaryLocation $BinaryLocation
            }
        }
        else
        {
            [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
            DisplayGUI
        }
    }
    else
    {
        Write-Host -Object " - We couldn't detect a SharePoint installation on this machine. Please execute the SharePoint ReverseDSC script on an existing SharePoint server." -BackgroundColor Red -ForegroundColor Black
    }
}

function Export-SPDscDiagnosticData
{
    [CmdletBinding(DefaultParametersetName = 'None')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0)]
        [System.String]
        $ExportFilePath,

        [Parameter()]
        [System.UInt32]
        $NumberOfDays = 7,

        [Parameter(ParameterSetName = 'Anon')]
        [Switch]
        $Anonymize,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Server,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Domain,

        [Parameter(ParameterSetName = 'Anon', Mandatory = $true)]
        [System.String]
        $Url
    )
    Write-Host 'Exporting logging information' -ForegroundColor Yellow

    $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -eq $false)
    {
        Write-Host -Object "[ERROR] You need to run this cmdlet with Administrator privileges!" -ForegroundColor Red
        return
    }

    $afterDate = (Get-Date).AddDays(($NumberOfDays * -1))

    # Create Temp folder
    $guid = [Guid]::NewGuid()
    $tempPath = Join-Path -Path $env:TEMP -ChildPath $guid
    $null = New-Item -Path $tempPath -ItemType 'Directory'

    # Copy DSC Verbose Logs
    Write-Host ' * Copying DSC Verbose Logs' -ForegroundColor Gray
    $logPath = Join-Path -Path $tempPath -ChildPath 'DSCLogs'
    $null = New-Item -Path $logPath -ItemType 'Directory'

    $sourceLogPath = Join-Path -Path $env:windir -ChildPath 'System32\Configuration\ConfigurationStatus'
    $items = Get-ChildItem -Path "$sourceLogPath\*.json" | Where-Object { $_.LastWriteTime -gt $afterDate }

    if ($null -ne $items)
    {
        Copy-Item -Path $items -Destination $logPath -ErrorAction 'SilentlyContinue' #-ErrorVariable $err
    }

    if ($Anonymize.IsPresent)
    {
        Write-Host ' * Anonymizing DSC Verbose Logs' -ForegroundColor Gray
        foreach ($file in (Get-ChildItem -Path $logPath))
        {
            $content = Get-Content -Path $file.FullName -Raw -Encoding Unicode
            $content = $content -replace $Domain, '[DOMAIN]' -replace $Url, 'fqdn.com' -replace $Server, '[SERVER]'
            Set-Content -Path $file.FullName -Value $content
        }
    }

    # Export SPDsc event log
    Write-Host ' * Exporting DSC Event Log' -ForegroundColor Gray
    $evtExportLog = Join-Path -Path $tempPath -ChildPath 'SPDsc.csv'

    try
    {
        Get-EventLog -LogName 'SPDsc' -After $afterDate | Export-Csv $evtExportLog -NoTypeInformation
        if ($Anonymize.IsPresent)
        {
            Write-Host ' * Anonymizing DSC Event Log' -ForegroundColor Gray
            $newLog = Import-Csv $evtExportLog
            foreach ($entry in $newLog)
            {
                $entry.MachineName = "[SERVER]"
                $entry.UserName = "[USER]"
                $entry.Message = $entry.Message -replace $Domain, '[DOMAIN]' -replace $Url, 'fqdn.com' -replace $Server, '[SERVER]'
            }

            $newLog | Export-Csv -Path $evtExportLog -NoTypeInformation
        }
    }
    catch
    {
        $txtExportLog = Join-Path -Path $tempPath -ChildPath 'SPDsc.txt'
        Add-Content -Value 'SPDsc event log does not exist!' -Path $txtExportLog
    }

    # PowerShell Version
    Write-Host ' * Exporting PowerShell Version info' -ForegroundColor Gray
    $psInfoFile = Join-Path -Path $tempPath -ChildPath 'PSInfo.txt'
    $PSVersionTable | Out-File -FilePath $psInfoFile

    # OS Version
    Write-Host ' * Exporting OS Version info' -ForegroundColor Gray
    $computerInfoFile = Join-Path -Path $tempPath -ChildPath 'OSInfo.txt'

    Get-ComputerInfo -Property @(
        'OsName',
        'OsOperatingSystemSKU',
        'OSArchitecture',
        'WindowsVersion',
        'WindowsBuildLabEx',
        'OsLanguage',
        'OsMuiLanguages') | Out-File -FilePath $computerInfoFile

    # LCM settings
    Write-Host ' * Exporting LCM Configuration info' -ForegroundColor Gray
    $lcmInfoFile = Join-Path -Path $tempPath -ChildPath 'LCMInfo.txt'
    Get-DscLocalConfigurationManager | Out-File -FilePath $lcmInfoFile

    # Creating export package
    Write-Host ' * Creating Zip file with all collected information' -ForegroundColor Gray

    if ((Split-Path -Path $ExportFilePath -Leaf) -like "*.*")
    {
        # ExportFilePath is file
        if ($ExportFilePath -match ".zip$")
        {
            $exportFilename = $ExportFilePath
        }
        else
        {
            # ExportFilePath does not have zip extension, correct to zip
            $exportFilename = $ExportFilePath -replace "\..*$", '.zip'
        }

        $parentFolder = Split-Path -Path $ExportFilePath
        if ((Test-Path -Path $parentFolder) -eq $false)
        {
            $null = New-Item -Path $parentFolder -ItemType Directory
        }
    }
    else
    {
        # ExportFilePath is folder
        $exportFilename = Join-Path -Path $ExportFilePath -ChildPath 'SPDsc.zip'

        if ((Test-Path -Path $ExportFilePath) -eq $false)
        {
            $null = New-Item -Path $ExportFilePath -ItemType Directory
        }
    }
    Compress-Archive -Path $tempPath -DestinationPath $exportFilename -Force

    # Cleaning up temporary data
    Write-Host ' * Removing temporary data' -ForegroundColor Gray
    Remove-Item $tempPath -Recurse -Force -Confirm:$false

    Write-Host ('Completed with export. Information exported to {0}' -f $exportFilename) -ForegroundColor Yellow
}

New-Alias -Name Start-SharePointDSCExtract -Value Export-SPConfiguration -Force