windows-csp/apply-mdm-policy.ps1

[CmdletBinding()]
param (
    [Parameter()]
    [string]$Token,
    [Parameter()]
    [string]$PRNumber,
    [Parameter()]
    [switch]$RetryAttempt = $false
)
    
begin {
    # Check for required PowerShell version (7+)
    if (!($PSVersionTable.PSVersion.Major -ge 7)) {
        try {
            # Install PowerShell 7 if missing
            if (!(Test-Path "$env:SystemDrive\Program Files\PowerShell\7")) {
                Write-Output '[INFO] Installing PowerShell version 7...'
                Invoke-Expression "& { $( Invoke-RestMethod https://aka.ms/install-powershell.ps1 ) } -UseMSI -Quiet"
            }
        
            # Refresh PATH
            $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
        
            # Restart script in PowerShell 7
            pwsh -File "`"$PSCommandPath`"" @PSBoundParameters
        
        }
        catch {
            Write-Output '[ERROR] PowerShell 7 was not installed. Update PowerShell and try again.'
            throw $Error
        }
        finally {
            exit $LASTEXITCODE
        }
    }
    else {
        # $PSStyle.OutputRendering = 'PlainText'
    }
    
    if ($env:token -and $env:token -notlike "null") {
        $Token = Ninja-Property-Get $env:token
    }
    
    if ($env:prNumber -and $env:prNumber -notlike "null") {
        $PRNumber = $env:prNumber
    }

    if (-not $Token) {
        Write-LogMessage "Please specify a Token." -LogType Error
        exit 1
    }

    function Set-PolicyFailure {
        Ninja-Property-Set latestPolicyFailure (Get-Date -UFormat %s)
    }

    function Write-LogMessage {
        param(
            [string]$Message, 
            [System.ConsoleColor]$Color = 'White',
            [string]$LogType = ""
        )
    
        $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'

        $LogType = $LogType.ToUpper()
        
        # Add prefix based on color
        [System.ConsoleColor]$consoleColor = switch ($LogType) {
            'ERROR' { 'Red' }
            'SUCCESS' { 'Green' }
            'WARNING' { 'Yellow' }
            'DEBUG' { 'Cyan' }
            default { 'White' }
        }

        $logMessage = "${timestamp}: [$LogType] $Message"

        # Console output
        if ($LogType -ne "DEBUG" -or $VerbosePreference -eq 'Continue') {
            Write-Host $logMessage -ForegroundColor $consoleColor
        }
    
        try {
            Add-Content -Path $LogFilePath -Value $logMessage -ErrorAction Stop
        }
        catch {
            Write-Host "Error writing to log file: $_" -ForegroundColor Red
        }
    }

    function Remove-XmlComments {
        param([System.Xml.XmlNode]$Node)
        foreach ($comment in @($Node.SelectNodes("//comment()"))) {
            $comment.ParentNode.RemoveChild($comment) | Out-Null
        }
    }

    function Rename-XmlElement {
        param(
            [System.Xml.XmlElement]$Element,
            [string]$NewName
        )
        $doc = $Element.OwnerDocument
        $newElement = $doc.CreateElement($NewName)

        # Copy attributes
        foreach ($attr in $Element.Attributes) {
            $newAttr = $doc.CreateAttribute($attr.Name)
            $newAttr.Value = $attr.Value
            $newElement.Attributes.Append($newAttr) | Out-Null
        }

        # Copy child nodes
        foreach ($child in $Element.ChildNodes) {
            $imported = $doc.ImportNode($child, $true)
            $newElement.AppendChild($imported) | Out-Null
        }

        # Replace in parent
        $parent = $Element.ParentNode
        $parent.ReplaceChild($newElement, $Element) | Out-Null

        return $newElement
    }
    
    
    function Get-PolicyFile {
        $accountname = "jreappstorage"
        $containername = "it-dept"
        $file = "policies/definitions.zip"

        $azureContext = New-AzStorageContext -StorageAccountName $accountname -StorageAccountKey $Token

        # if prnumber is not null, use it to get the file name
        if ($PRNumber -and $PRNumber -notlike "null") {
            $file = "policies/pr-$PRNumber/definitions.zip"
        }

        $ExpectedHash = (Get-AzStorageBlob -Container $containername -Blob $file -Context $azureContext).BlobProperties.ContentHash
        $ExpectedHash = [System.BitConverter]::ToString($ExpectedHash).Replace("-", "")

        $filePath = Join-Path $tempDir (Split-Path $file -leaf)

        # Download or verify existing file
        if ((Test-Path $filePath) -and 
            ((Get-FileHash -Path $filePath -Algorithm "MD5").Hash -eq $ExpectedHash)) {
            Write-LogMessage "Using existing verified file" -LogType Debug
        }
        else {
            Write-LogMessage "Downloading fresh copy from $accountname/$containername/$file..." -LogType Debug
            Remove-Item -Path $filePath -Force -ErrorAction SilentlyContinue
        
            try {
                Get-AzStorageBlobContent -Container $containername -Blob $file -Destination $filePath -Context $azureContext -Force | Out-Null
            
                $actualHash = (Get-FileHash -Path $filePath -Algorithm "MD5").Hash
                if ($actualHash -ne $ExpectedHash) {
                    throw "File hash verification failed. Expected: $ExpectedHash, Actual: $actualHash"
                }

                Write-LogMessage "File hash verified successfully" -LogType Debug
            }
            catch {
                throw "Failed to download or verify policy file: $_"
            }
        }

        # get the folder path from the file path
        $folderPath = Split-Path -Path $filePath -Parent

        $extractedFolder = Join-Path $folderPath ([System.IO.Path]::GetFileNameWithoutExtension($filePath))

        Write-LogMessage "Deleting files in: $extractedFolder" -LogType Debug

        # delete the folder if it exists
        if (Test-Path -Path $extractedFolder) {
            Remove-Item -Path $extractedFolder -Recurse -Force
        }

        Write-LogMessage "Extracting files in $filePath to: $folderPath" -LogType Debug

        # unzip the file
        Expand-Archive -Path $filePath -DestinationPath $folderPath -Force

        return $extractedFolder
    }

    function Test-StatusNodeFailure {
        param(
            [string]$DataNode,
            [string]$CmdRefNode,
            [string]$Action = ""
        )

        if ($DataNode -eq "200") {
            return $false
        }
        if ($DataNode -eq "418") {
            return $false
        }
        if ($DataNode -eq "500" -and $CmdRefNode -ieq "AllowHibernate") {
            return $false
        }
        if ($DataNode -eq "400" -and $CmdRefNode -ieq "AllNetworks_NetworkLocation") {
            return $false
        }
        if ($DataNode -eq "404" -and $Action -ieq "DELETE") {
            return $false
        }

        return $true
    }

    function Install-AzModule {
        # check if Az.Storage module is installed
        if (-not (Get-Module -ListAvailable -Name Az.Storage)) {
            Write-LogMessage "Installing Az.Storage module..." 

            # check to make sure the PSGallery is registered
            if (-not (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue)) {
                Write-LogMessage "Registering PSGallery repository..." 
                Register-PSRepository -Default
            }

            Install-Module -Name Az.Storage -Repository PSGallery -Scope CurrentUser -Force -AllowClobber

            # Refresh PATH
            $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
        
            if (!$RetryAttempt) {
                # Restart script in PowerShell 7
                Write-Host "RESTARTING SCRIPT IN POWERSHELL 7"
                $NewParams = @{}
                # Copy all bound parameters except RetryAttempt
                foreach ($key in $PSBoundParameters.Keys) {
                    if ($key -ne 'RetryAttempt') {
                        $NewParams[$key] = $PSBoundParameters[$key]
                    }
                }
                # Add RetryAttempt parameter with value $true
                $NewParams['RetryAttempt'] = $true
                pwsh -File $PSCommandPath @NewParams
            }
        }
        else {
            Write-LogMessage "Az.Storage module is already installed" 
        }
        # Import the module
        Import-Module -Name Az.Storage -Force -ErrorAction Stop
        Write-LogMessage "Az.Storage module imported successfully" -LogType Success
    }

    function Invoke-FormatPolicyString {
        param(
            [string]$Data,
            [string]$Format
        )
        if ($Format -eq "chr" -or $Format -ne "xml") {
            $Data = ($Data -replace '\s+', ' ').Trim()
        }

        if ($Data -match '[<>]') {
            # wrap in CDATA
            $Data = "<![CDATA[$($Data)]]>"
        }

        return $Data
    }

    #region -- Deleted Functions --
    # function Invoke-DeletePolicy {
    # param(
    # [PSObject]$Command
    # )
    # $Uris = @("./User/Vendor/MSFT/Policy/Config", "./Device/Vendor/MSFT/Policy/Config")

    # $ArgumentList = New-Object System.Collections.Generic.List[string]
    # $ArgumentList.Add("-OmaUri")
    # $ArgumentList.Add("./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall")

    # $xmlContent = [xml] (Start-ProcessEx -Arguments $ArgumentList)

    # $resultsNodes = $xmlContent.SelectNodes("//Results")

    # foreach ($result in $resultsNodes) {
    # $dataNode = $result.SelectSingleNode(".//Data")
            
    # # split the Data node value by '/'
    # $dataParts = $dataNode.InnerText -split '/'

    # # Log the individual parts
    # foreach ($part in $dataParts) {
    # $Uris += "./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/$part"
    # }
    # }

    # foreach ($uri in $Uris) {
    # $ArgumentList = New-Object System.Collections.Generic.List[string]
    # $ArgumentList.Add("-OmaUri")
    # $ArgumentList.Add($uri)
    # $ArgumentList.Add("-Command")
    # $ArgumentList.Add("DELETE")

    # $xmlContent = [xml] (Start-ProcessEx -Arguments $ArgumentList)

    # $statusNodes = $xmlContent.SelectNodes("//Status")
    
    # if (-not $statusNodes) {
    # Write-LogMessage "[$($uri)] No Status nodes found in the XML output." -LogType Error
    # $script:containsErrors = $true
    # $script:failedPolicies += $uri
    # }
            
    # foreach ($status in $statusNodes) {
    # $dataNode = $status.SelectSingleNode("Data")
    # $cmdNode = $status.SelectSingleNode("Cmd")
    # $cmdRefNode = $status.SelectSingleNode("CmdRef")

    # if ($cmdNode.InnerText -eq "SyncHdr") {
    # continue
    # }
                
    # if (Test-StatusNodeFailure -DataNode $dataNode.InnerText -CmdRefNode $cmdRefNode.InnerText -Action "DELETE") {
    # Write-LogMessage "[$($uri)] Policy deletion error - $($status.OuterXml)" -LogType Error
    # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'containsErrors', Justification = 'Used for error tracking')]
    # $script:containsErrors = $true
    # $script:failedPolicies += $uri
    # }
    # else {
    # Write-LogMessage "[$($uri)] Successfully deleted policy" -LogType Debug
    # }
    # }
    # }
    # }
    
    # function Start-ProcessEx {
    # param(
    # [string[]]$Arguments
    # )
            
    # $process = New-Object System.Diagnostics.Process
    # $psi = New-Object System.Diagnostics.ProcessStartInfo
    # $psi.FileName = $exePath
    # $psi.RedirectStandardInput = $true
    # $psi.RedirectStandardOutput = $true
    # $psi.RedirectStandardError = $true
    # $psi.UseShellExecute = $false
    # $psi.CreateNoWindow = $true
            
    # foreach ($arg in $Arguments) {
    # $psi.ArgumentList.Add($arg)
    # }
            
    # $process.StartInfo = $psi
    # $process.Start() | Out-Null
            
    # $output = $process.StandardOutput.ReadToEnd()
    # $err = $process.StandardError.ReadToEnd()
    # $process.WaitForExit()
            
    # if ($err) {
    # Write-Error $err
    # }
            
    # return $output
    # }
    #endregion

    function Invoke-ApplyPolicy {
        param(
            [string]$Filepath
        )

        $process = New-Object System.Diagnostics.Process
        $psi = New-Object System.Diagnostics.ProcessStartInfo
        $psi.FileName = $exePath
        $psi.RedirectStandardInput = $true
        $psi.RedirectStandardOutput = $true
        $psi.RedirectStandardError = $true
        $psi.UseShellExecute = $false
        $psi.CreateNoWindow = $true
        
        $psi.ArgumentList.Add("-SyncMLFile")
        $psi.ArgumentList.Add($Filepath)
        $process.StartInfo = $psi
        $process.Start() | Out-Null
        
        $output = $process.StandardOutput.ReadToEnd()
        $err = $process.StandardError.ReadToEnd()
        $process.WaitForExit()


        if ($err) {
            Write-Error $err
        }

        $xmlContent = [xml]$output

        $statusNodes = $xmlContent.SelectNodes("//Status")
    
        if (-not $statusNodes) {
            Write-LogMessage "[$($Filepath)] No Status nodes found in the XML output." -LogType Error
            $script:containsErrors = $true
            # $script:failedPolicies += $Command.Uri.InnerText
        }
            
        foreach ($status in $statusNodes) {
            $dataNode = $status.SelectSingleNode("Data")
            $cmdNode = $status.SelectSingleNode("Cmd")
            $cmdRefNode = $status.SelectSingleNode("CmdRef")

            if ($cmdNode.InnerText -eq "SyncHdr") {
                continue
            }
                
            if (Test-StatusNodeFailure -DataNode $dataNode.InnerText -CmdRefNode $cmdRefNode.InnerText -Action $cmdNode.InnerText) {                    
                Write-LogMessage "[$($cmdRefNode.InnerText) - $($cmdNode.InnerText.ToUpper())] - $($status.OuterXml)" -LogType Error
                $script:containsErrors = $true
                $script:failedPolicies += $cmdRefNode.InnerText
            }
            else {
                Write-LogMessage "[$($cmdRefNode.InnerText) - $($cmdNode.InnerText.ToUpper())]" -LogType Success
            }
        }
    }

    function Invoke-XmlProcessing {
        param(
            [string]$Path
        )

        $xmlContent = Get-Content -Path $Path -Raw

        # Find all policy nodes (Replace, Add, etc.) inside <SyncBody>
        $policyPattern = '<(Replace|Add|Delete|Exec)[^>]*>(.*?)</\1>'
        $policyMatches = [regex]::Matches($xmlContent, $policyPattern, 'Singleline')

        foreach ($policyMatch in $policyMatches) {
            $policyXml = $policyMatch.Value
            $policyType = $policyMatch.Groups[1].Value

            # Extract CmdID, LocURI, Data, Format, Type as strings
            $cmdId = [regex]::Match($policyXml, '<CmdID>(.*?)</CmdID>', 'Singleline').Groups[1].Value.Trim()
            $uri = [regex]::Match($policyXml, '<LocURI>(.*?)</LocURI>', 'Singleline').Groups[1].Value.Trim()
            $data = [regex]::Match($policyXml, '<Data>(.*?)</Data>', 'Singleline').Groups[1].Value.Trim()
            $format = [regex]::Match($policyXml, '<Format>(.*?)</Format>', 'Singleline').Groups[1].Value.Trim()
            $type = [regex]::Match($policyXml, '<Type>(.*?)</Type>', 'Singleline').Groups[1].Value.Trim()
            if (-not $type) { $type = 'text/plain' }

            $command = [PSCustomObject]@{
                CmdID   = $cmdId
                Uri     = $uri
                Node    = $policyXml
                Command = $policyType
                Data    = $data
                Format  = $format
                Type    = $type
            }

            # Handle <Filters> logic
            $filtersPattern = '<Filters>(.*?)</Filters>'
            $filtersMatch = [regex]::Match($policyXml, $filtersPattern, 'Singleline')
            if ($filtersMatch.Success) {
                $filtersXml = $filtersMatch.Groups[1].Value
                $minOSMatch = [regex]::Match($filtersXml, '<MinOSVersion>(.*?)</MinOSVersion>', 'Singleline')
                if ($minOSMatch.Success) {
                    $currentOS = [System.Environment]::OSVersion.Version
                    $minOS = [version]$minOSMatch.Groups[1].Value.Trim()
                    if ([version]$currentOS -lt [version]$minOS) {
                        Write-LogMessage "Skipping policy $($command.Uri) due to MinOSVersion filter ($currentOS < $minOS)" -LogType Debug

                        # remove the policy from xmlContent
                        $xmlContent = $xmlContent.Replace($policyMatch.Value, "")

                        continue
                    }
                }

                # --- NEW: Locations filter support (single current location) ---
                $locationsPattern = '<Locations>(.*?)</Locations>'
                $locationsMatch = [regex]::Match($filtersXml, $locationsPattern, 'Singleline')
                if ($locationsMatch.Success) {
                    # Current machine location (single value), e.g. NINJA_LOCATION_NAME=HQ
                    $currentLocation = $null
                    if ($env:NINJA_LOCATION_NAME) {
                        $currentLocation = $env:NINJA_LOCATION_NAME.Trim()
                    }

                    if ($currentLocation) {
                        $locationsXml = $locationsMatch.Groups[1].Value

                        $includeLocations = @()
                        foreach ($m in [regex]::Matches($locationsXml, '<Include>(.*?)</Include>', 'Singleline')) {
                            foreach ($loc in [regex]::Matches($m.Groups[1].Value, '<Location>(.*?)</Location>', 'Singleline')) {
                                $includeLocations += $loc.Groups[1].Value.Trim()
                            }
                        }

                        $excludeLocations = @()
                        foreach ($m in [regex]::Matches($locationsXml, '<Exclude>(.*?)</Exclude>', 'Singleline')) {
                            foreach ($loc in [regex]::Matches($m.Groups[1].Value, '<Location>(.*?)</Location>', 'Singleline')) {
                                $excludeLocations += $loc.Groups[1].Value.Trim()
                            }
                        }

                        $shouldSkipByLocation = $false

                        # If Include list is present, current location must be in it
                        if ($includeLocations.Count -gt 0) {
                            if (-not ($includeLocations -icontains $currentLocation)) {
                                $shouldSkipByLocation = $true
                            }
                        }

                        # Any match in Exclude list will skip the policy
                        if (-not $shouldSkipByLocation -and $excludeLocations.Count -gt 0) {
                            if ($excludeLocations -icontains $currentLocation) {
                                $shouldSkipByLocation = $true
                            }
                        }

                        if ($shouldSkipByLocation) {
                            Write-LogMessage ("Skipping policy {0} due to Locations filter. " +
                                "Current='{1}' Include='{2}' Exclude='{3}'" -f
                                $command.Uri,
                                $currentLocation,
                                ($includeLocations -join ','),
                                ($excludeLocations -join ',')
                            ) -LogType Debug

                            # remove the policy from xmlContent
                            $xmlContent = $xmlContent.Replace($policyMatch.Value, "")
                            continue
                        }
                    }
                }
                # --- END Locations filter support ---

                # Remove <Filters> from policyXml
                $policyXml = [regex]::Replace($policyXml, $filtersPattern, "", 'Singleline')
            }

            # Format policy data if needed (Invoke-FormatPolicy now expects string)
            $command.Data = Invoke-FormatPolicyString -Data $command.Data -Format $command.Format

            # Update the policyXml with the (potentially) modified Data value
            if ($command.Data) {
                # Replace the <Data>...</Data> content in the policyXml string
                $policyXml = [regex]::Replace(
                    $policyXml,
                    '<Data>.*?</Data>',
                    "<Data>$($command.Data)</Data>",
                    'Singleline'
                )
                # Replace the modified policyXml back into the xmlContent string
                $xmlContent = $xmlContent.Replace($policyMatch.Value, $policyXml)
            }
            # (Optional) Call Invoke-ApplyPolicy or other logic as needed
            # ...
        }

        # Save the modified XML string to a temp file and apply all policies at once
        $tempFile = "$tempDir\$([System.IO.Path]::GetFileName($Path))"
        Set-Content -Path $tempFile -Value $xmlContent
        Invoke-ApplyPolicy -Filepath $tempFile
    }

    function Invoke-RegProcessing {
        param(
            [string]$Path
        )

        $user = (Get-CimInstance Win32_ComputerSystem).UserName

        if ($user) {
            $account = New-Object System.Security.Principal.NTAccount($user)
            $sid = $account.Translate([System.Security.Principal.SecurityIdentifier]).Value
    
            (Get-Content $Path -Raw) -ireplace 'HKEY_CURRENT_USER', "HKEY_USERS\$sid" | Set-Content $Path
        }

        $process = Start-Process "regedit.exe" -ArgumentList "/s `"$Path`"" -Wait -PassThru

        if ($process.ExitCode -ne 0) {
            Write-LogMessage "[$($Path.Replace('C:\Windows\Temp', '')) failed with exit code: $($process.ExitCode)]" -LogType Error
            $script:containsErrors = $true
            $script:failedPolicies += $Path
        }
        else {
            Write-LogMessage "[$($Path.Replace('C:\Windows\Temp', ''))]" -LogType Success
        }
    }

    function Limit-LogFile {
        $LogSeparator = ('#' * 150)
        Write-LogMessage $LogSeparator -LogType Debug

        # if the log file is larger than 10MB, delete the first 5MB of the file
        if ((Test-Path -Path $LogFilePath) -and (Get-Item -Path $LogFilePath).Length -gt 10MB) {
            Write-LogMessage "Log file is larger than 10MB, trimming the file to 5MB" -LogType Debug
            
            # Read content, skip the first half, and write back to the file
            $content = Get-Content -Path $LogFilePath -Raw
            $bytesToRemove = (Get-Item -Path $LogFilePath).Length - 5MB
            if ($bytesToRemove -lt $content.Length) {
                $newContent = $content.Substring($bytesToRemove)
                Set-Content -Path $LogFilePath -Value $newContent -Force
            }
        }
    }

    $tempDir = Join-Path $env:TEMP "JRE-Ninja\policies"
    $LogFilePath = "$tempDir\MDMPolicies.log"
    $exePath = $null
    $script:containsErrors = $false
    $script:failedPolicies = @()
    
    if (-not (Test-Path $tempDir)) {
        New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
    }

    Write-LogMessage "Using directory: $tempDir"
}
process {
    try {
        Limit-LogFile

        Install-AzModule

        $PolicyFolder = Get-PolicyFile
        # $PolicyFolder = "C:\Users\kzeien\source\repos\windows-policy\definitions"

        # recursively find all .xml files in the folder (execute all files with 'ingest' in the name first)
        $PolicyFiles = Get-ChildItem -Path $PolicyFolder -Recurse -Filter "*.xml" | Where-Object { $_.Name -like "*ingest*" -and $_.Name -notlike "*ingest-boilerplate*" }

        # now add the rest of the xml files
        $PolicyFiles += Get-ChildItem -Path $PolicyFolder -Recurse -Filter "*.xml" | Where-Object { $_.Name -notlike "*ingest*" }

        $RegistryFiles = Get-ChildItem -Path $PolicyFolder -Recurse -Filter "*.reg"

        if (-not $PolicyFiles -and -not $RegistryFiles) {
            Write-LogMessage "No policy and registry files found in the specified folder." -LogType Error
            Set-PolicyFailure
            exit 1
        }

        $exePath = Get-ChildItem -Path $PolicyFolder -Recurse -Filter "*.exe" | Where-Object { $_.Name -like "*SyncMLViewer.Executer*" }
        if (-not $exePath) {
            Write-LogMessage "No executable file found in the specified folder." -LogType Error
            Set-PolicyFailure

            Write-LogMessage "Policy processing completed." -LogType Debug

            exit 1
        }

        # Invoke-DeletePolicy

        foreach ($file in $PolicyFiles) {
            Write-LogMessage "Processing policy file: $($file.FullName)" -LogType Debug
            Invoke-XmlProcessing -Path $file.FullName
        }

        foreach ($file in $RegistryFiles) {
            Write-LogMessage "Processing registry file: $($file.FullName)" -LogType Debug
            Invoke-RegProcessing -Path $file.FullName
        }

        if ($script:containsErrors) {
            Set-PolicyFailure

            Write-LogMessage "$($script:failedPolicies.Count) failed policies:" -LogType Error
            Write-LogMessage "Failed policies:`n$($script:failedPolicies -join "`n")" -LogType Error
            Write-LogMessage "Policy processing completed." -LogType Debug

            exit 1
        }
        
        Write-LogMessage "Policy processing completed." -LogType Debug

        exit 0

    }
    catch {
        Write-LogMessage "Failed to execute script: $_" -LogType Error
        Write-LogMessage "Error Line: $($_.InvocationInfo.ScriptLineNumber)" -LogType Error
        Write-LogMessage "Error Position: $($_.InvocationInfo.OffsetInLine)" -LogType Error
        Set-PolicyFailure
        Write-LogMessage "Policy processing completed." -LogType Debug

        exit 1
        
    }
}
end {

}