ManageApplocker.ps1

#Requires -RunAsAdministrator
<#PSScriptInfo
 
.VERSION 22.2.19.1
 
.GUID da1892c0-98ec-41e3-aaf5-f6cc767626e5
 
.AUTHOR christopher.strobel@outlook.de
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS Applocker
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>
 



<#
.SYNOPSIS
Wrapper script to manage AppLocker policy's/ruleset's in Local/Domain/Intune environments
.DESCRIPTION
Script to easily Manage Applocker in Intune, Domain, local
 
.PARAMETER Local
Select operationmode for Local AppLocker configuration
.PARAMETER Domain
Select operationmode for AppLocker configuration via Group Policy (make sure to prestage GPO with 'Applocker' string in name)
.PARAMETER Intune
Select operationmode for AppLocker configuration via custom OMA-URI in Intune (no prestage needed)
.PARAMETER File
Select operationmode for AppLocker configuration via xml file
.PARAMETER BuildBridgeScript
Build an Powershell script, which apply AppLocker ruleset based on hosted xml file on fileshare or url
.PARAMETER XmlSource
Define the source location for the AppLocker xml file. This location will be hardcoded in created bridge script and validated if the script runs.
.PARAMETER ScriptPath
Define the filepath for the generated bridge script.
.PARAMETER ReportFromForwarded
Create Out-Grid view of forwarded event's for better search experience
.PARAMETER ReportFromForwardedWithStatistic
Create Out-Grid view of forwarded event's for better search experience and statistic's
.PARAMETER CreateDefaultPolicy
Create the default AppLocker ruleset, which would also been created in msc "wizard" (in audit mode, so don't panic)
.PARAMETER ApplyToLocal
Apply ruleset from selected operationmode to local AppLocker ruleset for better editing/overview (Tip: I like to use an clean Windows Sandbox/VM)
.PARAMETER Merge
Merge selected rules from eventlog with existing ruleset from selected operationmode
.PARAMETER IntunePolicyName
Define name of Intune configuration profile (creates new if not exists)
.PARAMETER Ruletype
Select ruletype publish/hash/path for new rule
.PARAMETER PulishLocalPolicy
Replace existing ruleset with ruleset from local AppLocker configuration
.PARAMETER SwitchEnforcementMode
Switch enforcementmode of ruleset between notconfigured, auditonly or enabled
.PARAMETER ReturnXML
Return an xml object of current ruleset
.PARAMETER CleanupLocalPolicy
Cleanup local AppLocker ruleset from device
.PARAMETER CleanupLocalMDMPolicy
Cleanup wmi instance's created by AppLocker bridge script on local device
.PARAMETER BackupPolicy
Backup currrent ruleset as xml file
.EXAMPLE
.\ManageApplocker.ps1 -Local -CreateDefaultPolicy
 
Create default AppLocker ruleset in local policy store, which can be edit via secpol.msc
Also supported with -File -Domain -Intune
.EXAMPLE
.\ManageApplocker.ps1 -Intune -ApplyToLocal
 
Apply ruleset from Intune configuration profile to local AppLocker policy for better editing
Also supported with -File -Domain
.EXAMPLE
.\ManageApplocker.ps1 -Intune -Merge -IntunePolicyName "Custom configuration profile"
 
List rules from forwarded eventlog and merge selected rules with existing rules in intune configuration profile
Also supported with -File -Domain -Local
.EXAMPLE
.\ManageApplocker.ps1 -Intune -Merge -IntunePolicyName "Custom configuration profile"
 
Add selected rules to your existing rules in intune configuration profile
Also supported with -File -Domain -Local
.EXAMPLE
.\ManageApplocker.ps1 -Intune -PulishLocalPolicy
 
Upload your local Applocker policy to Intune
Also supported with -File -Domain
.EXAMPLE
.\ManageApplocker.ps1 -BuildBridgeScript -XmlSource "https://yourdomain.com/ruleset.xml"
 
Create an Powershell script, which loads the ruleset from "https://yourdomain.com/ruleset.xml" and apply this via mdm bridge.
 
Also supported with unc or local path like "\\yourserver\yourshare\share\ruleset.xml" or "c:\temp\ruleset.xml"
.EXAMPLE
.\ManageApplocker.ps1 -CleanupLocalPolicy
 
Remove the local AppLocker policy from system
.EXAMPLE
.\ManageApplocker.ps1 -CleanupLocalMDMPolicy
 
Remove AppLocker policy's, which assigned via mdm bridge
#>

[CmdletBinding()]
param (
    [Parameter(ParameterSetName = "ReportFromForwarded")]
    [Switch]$ReportFromForwarded,

    [Parameter(ParameterSetName = "ReportFromForwardedWithStatistic")]
    [Switch]$ReportFromForwardedWithStatistic,

    [Parameter(ParameterSetName = "Local-Create")]
    [Parameter(ParameterSetName = "Local-Apply")]
    [Parameter(ParameterSetName = "Local-Merge")]
    [Parameter(ParameterSetName = "Local-Switch")]
    [Parameter(ParameterSetName = "Local-Return")]
    [Switch]$Local,

    [Parameter(ParameterSetName = "Domain-Create")]
    [Parameter(ParameterSetName = "Domain-Apply")]
    [Parameter(ParameterSetName = "Domain-Merge")]
    [Parameter(ParameterSetName = "Domain-Switch")]
    [Parameter(ParameterSetName = "Domain-Return")]
    [Parameter(ParameterSetName = "Domain-Publish")]
    [Switch]$Domain,

    [Parameter(ParameterSetName = "Intune-Create")]
    [Parameter(ParameterSetName = "Intune-Apply")]
    [Parameter(ParameterSetName = "Intune-Merge")]
    [Parameter(ParameterSetName = "Intune-Switch")]
    [Parameter(ParameterSetName = "Intune-Return")]
    [Parameter(ParameterSetName = "Intune-Publish")]
    [Switch]$Intune,

    [Parameter(ParameterSetName = "Intune-Create")]
    [Parameter(ParameterSetName = "Intune-Merge")]
    [Parameter(ParameterSetName = "Intune-Publish")]
    [string]$IntunePolicyName,

    [Parameter(ParameterSetName = "File-Create")]
    [Parameter(ParameterSetName = "File-Apply")]
    [Parameter(ParameterSetName = "File-Merge")]
    [Parameter(ParameterSetName = "File-Switch")]
    [Parameter(ParameterSetName = "File-Return")]
    [Parameter(ParameterSetName = "File-Publish")]
    [Switch]$File,

    [Parameter(ParameterSetName = "File-Create")]
    [Parameter(ParameterSetName = "File-Apply")]
    [Parameter(ParameterSetName = "File-Merge")]
    [Parameter(ParameterSetName = "File-Switch")]
    [Parameter(ParameterSetName = "File-Return")]
    [Parameter(ParameterSetName = "File-Publish")]
    [System.IO.FileInfo]$Path = $(Join-Path -Path $PWD -ChildPath AppLockerRules.xml),

    [Parameter(ParameterSetName = "BuildBridgeScript")]
    [Switch]$BuildBridgeScript,

    [Parameter(Mandatory, ParameterSetName = "BuildBridgeScript")]
    [string]$XmlSource,

    [Parameter(ParameterSetName = "BuildBridgeScript")]
    [System.IO.FileInfo]$ScriptPath = $(Join-Path -Path $PWD -ChildPath maAppLBridgeScript.ps1),

    [Parameter(ParameterSetName = "Local-Create")]
    [Parameter(ParameterSetName = "Domain-Create")]
    [Parameter(ParameterSetName = "Intune-Create")]
    [Parameter(ParameterSetName = "File-Create")]
    [Switch]$CreateDefaultPolicy,

    [Parameter(ParameterSetName = "Domain-Apply")]
    [Parameter(ParameterSetName = "Intune-Apply")]
    [Parameter(ParameterSetName = "File-Apply")]
    [Switch]$ApplyToLocal,

    [Parameter(ParameterSetName = "Local-Merge")]
    [Parameter(ParameterSetName = "Domain-Merge")]
    [Parameter(ParameterSetName = "Intune-Merge")]
    [Parameter(ParameterSetName = "File-Merge")]
    [Switch]$Merge,

    [Parameter(ParameterSetName = "Local-Merge")]
    [Parameter(ParameterSetName = "Domain-Merge")]
    [Parameter(ParameterSetName = "Intune-Merge")]
    [Parameter(ParameterSetName = "File-Merge")]
    [ValidateSet("Publisher", "Path", "Hash")]
    [string]$Ruletype,

    [Parameter(ParameterSetName = "Local-Merge")]
    [Parameter(ParameterSetName = "Domain-Merge")]
    [Parameter(ParameterSetName = "Intune-Merge")]
    [Parameter(ParameterSetName = "File-Merge")]
    [string]$User = "Jeder",

    [Parameter(ParameterSetName = "Domain-Publish")]
    [Parameter(ParameterSetName = "Intune-Publish")]
    [Parameter(ParameterSetName = "File-Publish")]
    [switch]$PulishLocalPolicy,

    [Parameter(ParameterSetName = "Local-Switch")]
    [Parameter(ParameterSetName = "Domain-Switch")]
    [Parameter(ParameterSetName = "Intune-Switch")]
    [Parameter(ParameterSetName = "File-Switch")]
    [Switch]$SwitchEnforcementMode,

    [Parameter(ParameterSetName = "Local-Return")]
    [Parameter(ParameterSetName = "Domain-Return")]
    [Parameter(ParameterSetName = "Intune-Return")]
    [Parameter(ParameterSetName = "File-Return")]
    [Switch]$ReturnXML,

    [Parameter(ParameterSetName = "CleanupPolicy")]
    [Switch]$CleanupLocalPolicy,

    [Parameter(ParameterSetName = "CleanupLocalMDMPolicy")]
    [Switch]$CleanupLocalMDMPolicy,

    [Parameter(ParameterSetName = "BackupPolicy")]
    [ValidateSet("Domain", "Local", "Intune", "File")]
    [String]$BackupPolicy
)

# init
$ErrorActionPreference = 'Stop'
$host.PrivateData.VerboseForegroundColor = 'Cyan'

$DcName = $($env:LOGONSERVER -replace '\\', '' )
# default rules from wizard
[xml]$defaultAppLockerRulesXML = '<AppLockerPolicy Version="1"><RuleCollection Type="Appx" EnforcementMode="AuditOnly"><FilePublisherRule Id="a9e18c21-ff8f-43cf-b9fc-db40eed693ba" Name="(Standardregel) Alle signierten App-Pakete" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von signierten App-Paketen." UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePublisherCondition PublisherName="*" ProductName="*" BinaryName="*"><BinaryVersionRange LowSection="0.0.0.0" HighSection="*" /></FilePublisherCondition></Conditions></FilePublisherRule></RuleCollection><RuleCollection Type="Dll" EnforcementMode="NotConfigured" /><RuleCollection Type="Exe" EnforcementMode="AuditOnly"><FilePathRule Id="921cc481-6e17-4653-8f75-050b80acca20" Name="(Standardregel) Alle Dateien im Ordner &quot;Programme&quot;" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von Anwendungen, die sich im Ordner &quot;Programme&quot; befinden" UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePathCondition Path="%PROGRAMFILES%\*" /></Conditions></FilePathRule><FilePathRule Id="a61c8b2c-a319-4cd0-9690-d2177cad7b51" Name="(Standardregel) Alle Dateien im Ordner &quot;Windows&quot;" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von Anwendungen, die sich im Ordner &quot;Windows&quot; befinden" UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePathCondition Path="%WINDIR%\*" /></Conditions></FilePathRule><FilePathRule Id="fd686d83-a829-4351-8ff4-27c7de5755d2" Name="(Standardregel) Alle Dateien" Description="Erm?glicht Mitgliedern der lokalen Administratorgruppe das Ausf?hren aller Anwendungen" UserOrGroupSid="S-1-5-32-544" Action="Allow"><Conditions><FilePathCondition Path="*" /></Conditions></FilePathRule></RuleCollection><RuleCollection Type="Msi" EnforcementMode="AuditOnly"><FilePublisherRule Id="b7af7102-efde-4369-8a89-7a6a392d1473" Name="(Standardregel) Alle digital signierten Windows Installer-Dateien" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von digital signierten Windows Installer-Dateien" UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePublisherCondition PublisherName="*" ProductName="*" BinaryName="*"><BinaryVersionRange LowSection="0.0.0.0" HighSection="*" /></FilePublisherCondition></Conditions></FilePublisherRule><FilePathRule Id="5b290184-345a-4453-b184-45305f6d9a54" Name="(Standardregel) Alle Windows Installer-Dateien unter &quot;%systemdrive%\Windows\Installer&quot;" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren aller Windows Installer-Dateien, die sich unter &quot;%systemdrive%\Windows\Installer&quot; befinden." UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePathCondition Path="%WINDIR%\Installer\*" /></Conditions></FilePathRule><FilePathRule Id="64ad46ff-0d71-4fa0-a30b-3f3d30c5433d" Name="(Standardregel) Alle Windows Installer-Dateien" Description="Erm?glicht Mitgliedern der lokalen Administratorgruppe das Ausf?hren aller Windows Installer-Dateien" UserOrGroupSid="S-1-5-32-544" Action="Allow"><Conditions><FilePathCondition Path="*.*" /></Conditions></FilePathRule></RuleCollection><RuleCollection Type="Script" EnforcementMode="AuditOnly"><FilePathRule Id="06dce67b-934c-454f-a263-2515c8796a5d" Name="(Standardregel) Alle Skripts im Ordner &quot;Programme&quot;" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von Skripts, die sich im Ordner &quot;Programme&quot; befinden" UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePathCondition Path="%PROGRAMFILES%\*" /></Conditions></FilePathRule><FilePathRule Id="9428c672-5fc3-47f4-808a-a0011f36dd2c" Name="(Standardregel) Alle Skripts im Ordner &quot;Windows&quot;" Description="Erm?glicht Mitgliedern der Gruppe &quot;Jeder&quot; das Ausf?hren von Skripts, die sich im Ordner &quot;Windows&quot; befinden" UserOrGroupSid="S-1-1-0" Action="Allow"><Conditions><FilePathCondition Path="%WINDIR%\*" /></Conditions></FilePathRule><FilePathRule Id="ed97d0cb-15ff-430f-b82c-8d7832957725" Name="(Standardregel) Alle Skripts" Description="Erm?glicht Mitgliedern der lokalen Administratorgruppe das Ausf?hren aller Skripts" UserOrGroupSid="S-1-5-32-544" Action="Allow"><Conditions><FilePathCondition Path="*" /></Conditions></FilePathRule></RuleCollection></AppLockerPolicy>'

#region internal function's
function Get-maApplockerGPO {
    try {
        if ([string]::IsNullOrEmpty($(Get-PSSession -ComputerName $DcName))) {
            Write-Verbose -Message "Create target GPOs"
            $dcSession = New-PSSession -ComputerName $DcName
            Import-PSSession -Session $dcSession -Module GroupPolicy | Out-Null
        }
        Write-Verbose -Message "List target GPOs"
        $GPO = get-gpo -all | Where-Object displayname -Like "*Applocker*" | Select-Object displayname, path | Out-GridView -PassThru -Title "Select target GPO to apply rules"
        if ([string]::IsNullOrEmpty($GPO)) { Write-Warning "Sry, you have to select one target GPO, if no one exists create one with 'AppLocker' in name"; break }

        return $GPO
    } catch {
        $_.Exception.Message
    } finally {
        Write-Verbose -Message "Cleanup PSSessions..."
        Get-PSSession | Remove-PSSession
    }
}
function Get-maLDAPPath {
    if ([string]::IsNullOrEmpty($DcName)) {
        Write-Warning "No logonserer found in `$env:LOGONSERVER variable, are you realy on an ad device with ad user?"
        break
    }
    "LDAP://" + "$DcName" + "." + "$ENV:USERDNSDOMAIN" + "/" + "$((Get-maApplockerGPO).Path)"
}
function Convertto-maBase64 {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $Object
    )
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Object)
    [Convert]::ToBase64String($bytes)
}
function ConvertFrom-maBase64 {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $Base64String
    )
    $bytes = [convert]::FromBase64String($Base64String)
    [System.Text.Encoding]::UTF8.GetString($bytes)
}
function Select-maRuletype {
    if (!$Ruletype) {
        $Ruletype = "Publisher", "Path", "Hash" | Out-GridView -Title "Please select needed rule type" -OutputMode Single
        if ([string]::IsNullOrEmpty($Ruletype)) { Write-Warning "Sry, you have to select one rule type"; break }
    }
    return $Ruletype
}
function Select-maEnforcementMode {
    $selectedMode = "NotConfigured", "AuditOnly", "Enabled" | Out-GridView -Title "Select your enforcementmode, witch you want to set" -OutputMode Single
    if (!$selectedMode) {
        Write-Warning "Sry, you have to select an enforcementmode"
        break
    }
    return $selectedMode
}
function Select-maAppLockerRules {
    $fwRules = Get-AppLockerFileInformation -EventLog -LogPath ForwardedEvents | Out-GridView -PassThru
    if ([string]::IsNullOrEmpty($fwRules)) {
        Write-Warning "Sry, you have to select some rules"
        break
    }
    return $fwRules
}
function Get-maIntunePolicy {
    param (
        $PolicyName
    )
    # check for intune module
    $intuneModule = Get-Module -Name Microsoft.Graph.Intune -ListAvailable
    if ([string]::IsNullOrEmpty($intuneModule)) {
        Install-Module -Name Microsoft.Graph.Intune -Force -Confirm:$false
    }

    Connect-MSGraph -ErrorAction Stop | Out-Null
    if ([string]::IsNullOrEmpty($PolicyName)) {
        Write-Verbose "No Policyname parsed, list available profiles with 'applocker' in name..."
        $config = Get-DeviceManagement_DeviceConfigurations | Where-Object displayname -Like "*applocker*" | Select-Object displayname, description, omaSettings | Out-GridView -Title "Select intune configuration policy" -OutputMode Single
    } else {
        Write-Verbose "Query profile with with $PolicyName from Intune"
        $config = Get-DeviceManagement_DeviceConfigurations -Filter "displayName eq '$PolicyName'"
    }
    if ([string]::IsNullOrEmpty($config)) {
        Write-Warning "Sry, no Intune-Configuration with name $PolicyName found..."
        break
    }
    return $Config
}
function Get-maIntunePolicyXML {
    param (
        $PolicyObject
    )
    # create new AppLocker xml
    $intuneXML = New-Object Xml
    $applockerNode = $intuneXML.CreateElement('AppLockerPolicy')
    $intuneXML.AppendChild($applockerNode) | Out-Null
    $applockerNode.SetAttribute('Version', "1")

    $PolicyObject.omaSettings | ForEach-Object {
        [xml]$xmlToAdd = ConvertFrom-maBase64 -Base64String $_.value
        $rulesetNode = $intuneXML.ImportNode($xmlToAdd.DocumentElement, $true)
        $intuneXML.AppLockerPolicy.AppendChild($rulesetNode) | Out-Null
    }
    return $intuneXML
}
function Publish-maIntunePolicy {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [xml]$AppLockerXML,

        [Parameter(Mandatory, ValueFromPipeline)]
        [string]$PolicyName
    )
    begin {
        # check for intune module
        $intuneModule = Get-Module -Name Microsoft.Graph.Intune -ListAvailable
        if ([string]::IsNullOrEmpty($intuneModule)) {
            Install-Module -Name Microsoft.Graph.Intune -Force -Confirm:$false
        }

        # connect to graph
        Connect-MSGraph -ErrorAction Stop | Out-Null

        # check if configuration already exists
        $checkConfig = get-DeviceManagement_DeviceConfigurations -Filter "displayname eq '$PolicyName'"
        if ($checkConfig) {
            Write-Warning "Deviceconfiguration with name $IntunePolicyName already exists, will be updated "
        }
    }
    process {
        $ErrorActionPreference = 'stop'
        try {
            $omaSettings = @()
            $AppLockerXML.AppLockerPolicy.RuleCollection | Where-Object EnforcementMode -NE "NotConfigured" | ForEach-Object {
                Write-Verbose "Create ruleset for $($_.Type)"
                Write-Verbose "Processing XML for $($_.Type) : `n$($_.OuterXml)"
                switch ($_.Type) {
                    Appx { $omaPath = "StoreApps"; break }
                    Dll { $omaPath = "DLL"; break }
                    Exe { $omaPath = "EXE"; break }
                    Msi { $omaPath = "MSI"; break }
                    Script { $omaPath = "SCRIPT"; break }
                }
                $omaName = "Policy for $omaPath"
                $omaUri = "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/$omaPath`Group/$omaPath/Policy"
                $encOmaXml = $_.OuterXml | Convertto-maBase64

                $omaSetting = New-OmaSettingObject -displayName "Applocker - $omaPath" `
                    -description $omaName `
                    -omaUri $omaUri `
                    -omaSettingStringXml `
                    -value $encOmaXml

                $omaSettings += $omaSetting
                Write-Host -ForegroundColor Green "Create ruleset for $($_.Type) done"
            }

            # create/update intune device configuration
            $configParams = @{
                windows10CustomConfiguration = $true
                displayName                  = $PolicyName
                description                  = "Applying AppLocker policy via mdm, using event forwarding for better management"
                omaSettings                  = $omaSettings
            }
            if ($checkConfig) {
                Update-DeviceManagement_DeviceConfigurations -deviceConfigurationId $checkConfig.id @configParams
            } else {
                New-DeviceManagement_DeviceConfigurations @configParams
            }
        } catch {
            $_.Exception.Message
        }
    }
}
function Merge-maAppLockerXML {
    [CmdletBinding()]
    param (
        [xml[]]$XMLs
    )

    begin {
        Write-Verbose "Create new AppLocker xml for output"
        # create new AppLocker xml
        $finalXml = New-Object Xml
        $applockerNode = $finalXml.CreateElement('AppLockerPolicy')
        $finalXml.AppendChild($applockerNode) | Out-Null
        $applockerNode.SetAttribute('Version', "1")

        "Appx", "Dll", "Exe", "Msi", "Script" | ForEach-Object {
            $node = $finalXml.CreateElement('RuleCollection')
            $finalXml.AppLockerPolicy.AppendChild($node) | Out-Null
            $node.SetAttribute('Type', $_)
            $node.SetAttribute('EnforcementMode', 'NotConfigured')
        }
        Write-Verbose $finalXml.OuterXml
    }

    process {
        foreach ($xml in $XMLs) {
            # validate AppLocker xml
            if (!$xml.AppLockerPolicy) { Write-Warning "Sry this is not an AppLocker XML"; return }

            # loop through rulecollection
            foreach ($collection in $xml.AppLockerPolicy.RuleCollection) {

                # set enforcementmode
                $parentNode = $finalXml.AppLockerPolicy.RuleCollection.Where( { $_.Type -eq $collection.Type })
                if (($parentNode.EnforcementMode -eq 'NotConfigured') -or ($parentNode.EnforcementMode -eq 'AuditOnly')) {
                    $parentNode.SetAttribute('EnforcementMode', $($collection.EnforcementMode))
                }

                # loop through elements
                foreach ($element in $collection.ChildNodes) {

                    if ($parentNode.ChildNodes.id -contains $element.Id) {
                        Write-Verbose "Element already present"
                    } else {
                        $node = $finalXml.ImportNode($element, $true)
                        $parentNode.AppendChild($node) | Out-Null
                        Write-Verbose "Element added"
                    }
                }
            }
        }
        Write-Verbose $finalXml.OuterXml
        $finalXml
    }
}
#rework for Microsoft.Graph.DeviceManagement module
function Get-mgIntuneOmaSetPlainValue {
    param (
        $DeviceConfigurationId,
        $SecretReferenceValueId
    )
    # build url
    $baseUrl = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/"
    $configUrl = $baseUrl + $DeviceConfigurationId
    $plainUrl = $configUrl + "/getOmaSettingPlainTextValue(secretReferenceValueId='$SecretReferenceValueId')"

    $result = Invoke-mgGraphRequest -Uri $plainUrl
    return $result.value
}
function Get-mgIntunePolicy {
    param (
        $PolicyName
    )
    # check for intune module
    $graphModule = Get-InstalledModule -Name Microsoft.Graph.DeviceManagement -ErrorAction SilentlyContinue
    if ([string]::IsNullOrEmpty($graphModule)) {
        Install-Module -Name Microsoft.Graph.DeviceManagement -Force -Confirm:$false
    }
    # connect to microsoft graph
    Connect-MgGraph -Scopes DeviceManagementConfiguration.ReadWrite.All -ErrorAction Stop | Out-Null

    if ([string]::IsNullOrEmpty($PolicyName)) {
        Write-Verbose "No Policyname parsed, list available profiles with 'applocker' in name..."
        $configID = Get-MgDeviceManagementDeviceConfiguration -Filter "contains(displayName,'Applocker')" | Select-Object displayname, description, Id | Out-GridView -Title "Select intune configuration policy" -OutputMode Single | Select-Object -ExpandProperty id
    } else {
        Write-Verbose "Query profile with with $PolicyName from Intune"
        $configID = Get-MgDeviceManagementDeviceConfiguration -Filter "displayName eq '$PolicyName'" | Select-Object -ExpandProperty id
    }
    if ([string]::IsNullOrEmpty($configID)) {
        Write-Warning "Sry, no Intune-Configuration with name $PolicyName found..."
        break
    }
    ## have to use Invoke-mgGraphRequest to get full item with secretReferenceValueId property
    $baseUrl = "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/"
    $configUrl = $baseUrl + $configID
    $config = Invoke-mgGraphRequest -Uri $configUrl

    return $Config
}
function Get-mgIntunePolicyXML {
    param (
        $PolicyObject
    )
    # create new AppLocker xml
    $intuneXML = New-Object Xml
    $applockerNode = $intuneXML.CreateElement('AppLockerPolicy')
    $intuneXML.AppendChild($applockerNode) | Out-Null
    $applockerNode.SetAttribute('Version', "1")

    $PolicyObject.omaSettings | ForEach-Object {
        $refID = $_.secretReferenceValueId
        [xml]$xmlToAdd = Get-mgIntuneOmaSetPlainValue $PolicyObject.id $refID
        $rulesetNode = $intuneXML.ImportNode($xmlToAdd.DocumentElement, $true)
        $intuneXML.AppLockerPolicy.AppendChild($rulesetNode) | Out-Null
    }
    return $intuneXML
}

#endregion

# Backup
if ($PSBoundParameters.Keys.Contains("BackupPolicy")) {
    $LogDate = (Get-Date).ToString("dd.MM.yyyy-HH.mm")
    $Filename = $env:COMPUTERNAME + "_" + $LogDate + ".xml"
    if ($PSBoundParameters.BackupPolicy -eq "Intune") {
        Write-Verbose -Message "Backup Intune policyruleset"
        $intunePolicy = Get-maIntunePolicy -PolicyName $IntunePolicyName
        $intunePolicyXML = Get-maIntunePolicyXML -PolicyObject $intunePolicy
        $intunePolicyXML.Save($(Join-Path -Path $PWD -ChildPath  "intune_$Filename"))
    } elseif ($PSBoundParameters.BackupPolicy -eq "Domain") {
        Write-Verbose -Message "Backup domain policyruleset"
        $LDAPPath = Get-maLDAPPath
        [xml]$domainPolicyXML = Get-AppLockerPolicy -Ldap $LDAPPath -Domain -Xml
        $domainPolicyXML.Save($(Join-Path -Path $PWD -ChildPath  "domain_$Filename"))
    } else {
        Write-Verbose -Message "Backup local policyruleset"
        [xml]$localPolicyXML = Get-AppLockerPolicy -Local -Xml
        $localPolicyXML.Save($(Join-Path -Path $PWD -ChildPath  "local_$Filename"))
    }
}
# Report
if ($ReportFromForwarded.IsPresent) {
    Write-Verbose -Message "Create Report from forwarded eventlog and pipe to ogw"
    Get-AppLockerFileInformation -EventLog -LogPath ForwardedEvents | Out-GridView
}
if ($ReportFromForwardedWithStatistic.IsPresent) {
    Write-Verbose -Message "Create Report with statistics from forwarded eventlog and pipe to ogw"
    Get-AppLockerFileInformation -EventLog -LogPath ForwardedEvents -Statistics | Out-GridView
}
# Create
if ($CreateDefaultPolicy.IsPresent) {
    if ($Intune.IsPresent) {
        try {
            if ([string]::IsNullOrEmpty($IntunePolicyName)) {
                $IntunePolicyName = 'Configure - AppLocker policy via mdm'
                Write-Verbose -Message "No Intune profilename entered, default value: $IntunePolicyName"
            }
            Write-Verbose -Message "Create default AppLocker policy in Intune"
            Publish-maIntunePolicy -PolicyName $IntunePolicyName -AppLockerXML $defaultAppLockerRulesXML
        } catch {
            $_.Exception.Message
        }
    } elseif ($Domain.IsPresent) {
        try {
            Write-Verbose -Message "Create default AppLocker policy on group policy for domain"
            $tempXmlFile = Join-Path -Path $env:TEMP -ChildPath defaultrules.xml
            $LDAPPath = Get-maLDAPPath
            $defaultAppLockerRulesXML.Save($tempXmlFile)
            Set-AppLockerPolicy -XmlPolicy $tempXmlFile -Ldap $LDAPPath
            Remove-Item -Path $tempXmlFile -Force
        } catch {
            $_.Exception.Message
        }
    } elseif ($File.IsPresent) {
        try {
            Write-Verbose -Message "Create default AppLocker policy xml file on $Path"
            $defaultAppLockerRulesXML.Save($Path)
        } catch {
            $_.Exception.Message
        }
    } else {
        Write-Verbose -Message "Create default AppLocker policy on local device"
        $tempXmlFile = Join-Path -Path $env:TEMP -ChildPath defaultrules.xml
        $defaultAppLockerRulesXML.Save($tempXmlFile)
        Set-AppLockerPolicy -XmlPolicy $tempXmlFile
        Remove-Item -Path $tempXmlFile -Force
        secpol.msc
    }
}
# Merge
if ($Merge.IsPresent) {
    if ($Intune.IsPresent) {
        try {
            $intunePolicy = Get-maIntunePolicy -PolicyName $IntunePolicyName
            $intunePolicyXML = Get-maIntunePolicyXML -PolicyObject $intunePolicy

            Write-Verbose "Receive AppLockerInformation from eventlog"
            $newRules = Select-maAppLockerRules
            $mergeXML = $newRules | New-AppLockerPolicy -RuleType $(Select-maRuletype) -IgnoreMissingFileInformation -Optimize -Xml

            $finalXML = Merge-maAppLockerXML -XMLs $intunePolicyXML, $mergeXML
            Write-Verbose -Message "Merged selected rules with existing rules in Intune `n$($finalXML.OuterXml)"
            Publish-maIntunePolicy -AppLockerXML $finalXML -PolicyName $IntunePolicyName
        } catch {
            $_.Exception.Message
        }
    } elseif ($Domain.IsPresent) {
        try {
            $LDAPPath = Get-maLDAPPath
            Write-Verbose -Message "Merge selected rules with existing rules and apply to local policy"

            $newRules = Select-maAppLockerRules
            $newRules | New-AppLockerPolicy -RuleType $(Select-maRuletype) -User $User -IgnoreMissingFileInformation -Optimize | Set-AppLockerPolicy -Ldap $LDAPPath -Merge
        } catch {
            $_.Exception.Message
        }
    } elseif ($File.IsPresent) {
        try {
            [xml]$filePolicyXML = Get-Content -Path $Path

            Write-Verbose -Message "Merge selected rules with existing rules in target file"
            $newRules = Select-maAppLockerRules
            $mergeXML = $newRules | New-AppLockerPolicy -RuleType $(Select-maRuletype) -IgnoreMissingFileInformation -Optimize -Xml

            $finalXML = Merge-maAppLockerXML -XMLs $filePolicyXML, $mergeXML
            Write-Verbose -Message "Merged selected rules with existing rules on target file"
            $finalXML.Save($Path)
        } catch {
            $_.Exception.Message
        }
    } else {
        Write-Verbose -Message "Merge selected rules with existing rules and apply to local policy"

        $newRules = Select-maAppLockerRules
        $newRules | New-AppLockerPolicy -RuleType $(Select-maRuletype) -User $User -IgnoreMissingFileInformation -Optimize | Set-AppLockerPolicy -Merge
    }
}
# Apply
if ($ApplyToLocal.IsPresent) {
    if ($Intune.IsPresent) {
        try {
            Write-Verbose -Message "Apply Intune policy to local policy"
            $intunePolicy = Get-mgIntunePolicy -PolicyName $IntunePolicyName
            $intunePolicyXML = Get-mgIntunePolicyXML -PolicyObject $intunePolicy

            # apply intune to local
            $intunePolicyXML.OuterXml | Out-File -FilePath $env:TEMP\applockerFile.xml -Force
            Set-AppLockerPolicy -XmlPolicy $env:TEMP\applockerFile.xml
            Remove-Item -Path $env:TEMP\applockerFile.xml -Force
            secpol.msc
        } catch {
            $_.Exception.Message
        }
    } elseif ($Domain.IsPresent) {
        try {
            Write-Verbose -Message "Apply domain policy to local policy"
            $LDAPPath = Get-maLDAPPath

            # apply intune to local
            Get-AppLockerPolicy -Domain -Ldap $LDAPPath | Set-AppLockerPolicy
            secpol.msc
        } catch {
            $_.Exception.Message
        }
    }
    # else file mode (already filtered by parameterset)
    else {
        try {
            Write-Verbose -Message "Apply ruleset from AppLocker file localy"
            Set-AppLockerPolicy -XmlPolicy $Path
            secpol.msc
        } catch {
            $_.Exception.Message
        }
    }
}
# Publish
if ($PulishLocalPolicy.IsPresent) {
    if ($Intune.IsPresent) {
        try {
            $intunePolicy = Get-maIntunePolicy -PolicyName $IntunePolicyName
            Write-Verbose -Message "Get local policy rules"
            [xml]$localPolicyXML = Get-AppLockerPolicy -Local -Xml

            Write-Verbose -Message "Upload local policy rules to Intune"
            Publish-maIntunePolicy -AppLockerXML $localPolicyXML -PolicyName $intunePolicy.displayName
        } catch {
            $_.Exception.Message
        }
    } elseif ($Domain.IsPresent) {
        try {
            $LDAPPath = Get-maLDAPPath
            Write-Verbose -Message "Get local policy rules"
            $localPolicy = Get-AppLockerPolicy -Local

            Write-Verbose -Message "Upload local policy to domain"
            $localPolicy | Set-AppLockerPolicy -Domain -Ldap $LDAPPath
        } catch {
            $_.Exception.Message
        }
    }
    # else file mode (already filtered by parameterset)
    else {
        try {
            Write-Verbose -Message "Get local ruleset"
            [xml]$localPolicyXML = Get-AppLockerPolicy -Local -Xml

            Write-Verbose -Message "Override target file $($Path.Fullname) with new one"
            $localPolicyXML.Save($Path)
        } catch {
            $_.Exception.Message
        }
    }
}
# Switch
if ($SwitchEnforcementMode.IsPresent) {
    if ($Intune.IsPresent) {
        Write-Verbose "Query device configuration profiles"
        $editPolicy = Get-maIntunePolicy
        $editXML = Get-maIntunePolicyXML -PolicyObject $editPolicy

        # select/edit enforcementmode
        $enforcementMode = Select-maEnforcementMode
        $editXML.AppLockerPolicy.RuleCollection | Out-GridView -Title "Select ruleset you want to edit" -PassThru | ForEach-Object { $_.EnforcementMode = "$enforcementMode" }

        # upload new configuration
        Publish-maIntunePolicy -AppLockerXML $editXML -PolicyName $editPolicy.displayName
    } elseif ($Domain.IsPresent) {
        try {
            Write-Verbose -Message "Query group policy's"
            $LDAPPath = Get-maLDAPPath
            [xml]$editXML = Get-AppLockerPolicy -Ldap $LDAPPath -Xml

            # select/edit enforcementmode
            $enforcementMode = Select-maEnforcementMode
            $editXML.AppLockerPolicy.RuleCollection | Out-GridView -Title "Select ruleset you want to edit" -PassThru | ForEach-Object { $_.EnforcementMode = "$enforcementMode" }

            # save file temporarily
            $tempXmlFile = Join-Path -Path $env:TEMP -ChildPath defaultrules.xml
            $editXML.Save($tempXmlFile)

            # Set domain policy
            Set-AppLockerPolicy -Ldap $LDAPPath -XmlPolicy $tempXmlFile
            Remove-Item -Path $tempXmlFile -Force
        } catch {
            $_.Exception.Message
        }
    } elseif ($File.IsPresent) {
        try {
            Write-Verbose -Message "Query group policy's"
            [xml]$editXML = Get-Content -Path $Path

            # select/edit enforcementmode
            $enforcementMode = Select-maEnforcementMode
            $editXML.AppLockerPolicy.RuleCollection | Out-GridView -Title "Select ruleset you want to edit" -PassThru | ForEach-Object { $_.EnforcementMode = "$enforcementMode" }

            Write-Verbose -Message "Save changes if done"
            $editXML.Save($Path)
        } catch {
            $_.Exception.Message
        }
    } else {
        try {
            Write-Verbose -Message "Merge selected rules with existing rules and apply to local policy"
            [xml]$editXML = Get-AppLockerPolicy -Local -Xml

            # select/edit enforcementmode
            $enforcementMode = "NotConfigured", "AuditOnly", "Enabled" | Out-GridView -Title "Select your enforcementmode, witch you want to set" -OutputMode Single
            if (!$enforcementmode) { Write-Warning "Sry, you have to select an enforcementmode"; break }
            $editXML.AppLockerPolicy.RuleCollection | Out-GridView -Title "Select ruleset you want to edit" -PassThru | ForEach-Object { $_.EnforcementMode = "$enforcementMode" }

            # save file temporarily
            $tempXmlFile = Join-Path -Path $env:TEMP -ChildPath editrules.xml
            $editXML.Save($tempXmlFile)

            # Set domain policy
            Set-AppLockerPolicy -XmlPolicy $tempXmlFile
            Remove-Item -Path $tempXmlFile -Force
        } catch {
            $_.Exception.Message
        }
    }
}
# Return
if ($ReturnXML.IsPresent) {
    if ($Intune.IsPresent) {
        try {
            Write-Verbose -Message "Return Intune XML AppLocker policy in Intune"
            $intunePolicy = Get-maIntunePolicy -PolicyName $IntunePolicyName
            $intunePolicyXML = Get-maIntunePolicyXML -PolicyObject $intunePolicy

            return $intunePolicyXML
        } catch {
            $_.Exception.Message
        }
    } elseif ($Domain.IsPresent) {
        try {
            Write-Verbose -Message "Return domain XML AppLocker policy"
            $LDAPPath = Get-maLDAPPath
            [xml]$domainPolicyXML = Get-AppLockerPolicy -Ldap $LDAPPath -Domain -Xml

            return $domainPolicyXML
        } catch {
            $_.Exception.Message
        }
    }
    # else local mode (already filtered by parameterset)
    else {
        Write-Verbose -Message "Return local XML AppLocker policy"
        [xml]$localPolicyXML = Get-AppLockerPolicy -Local -Xml

        return $localPolicyXML
    }
}
# Cleanup
if ($CleanupLocalPolicy.IsPresent) {
    try {
        Set-AppLockerPolicy -PolicyObject ([Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.AppLockerPolicy]::new())
        Write-Verbose "Cleaning local AppLocker policy successfully"
    } catch {

    }

}
if ($CleanupLocalMDMPolicy.IsPresent) {
    try {
        Write-Verbose "Cleaning local MDM AppLocker policy"
        $namespaceName = "root\cimv2\mdm\dmmap"
        $GroupName = "AppLockerMDM"
        $parentID = "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/$GroupName"
        @(
            "MDM_AppLocker_ApplicationLaunchRestrictions01_StoreApps03"
            "MDM_AppLocker_DLL03"
            "MDM_AppLocker_ApplicationLaunchRestrictions01_EXE03"
            "MDM_AppLocker_MSI03"
            "MDM_AppLocker_Script03"
        ) | ForEach-Object {
            Write-Verbose "Checking for instance in $_"
            if ($curInstance = Get-CimInstance -Namespace $namespaceName -ClassName $_) {
                Write-Verbose "Cleanup MDM policy for class $($_)"
                Remove-CimInstance -InputObject $curInstance
            }
        }
        Write-Verbose "All AppLocker MDM policy's removed from local device, removing files from AppLocker folder"
        Remove-Item -Path $(Join-Path -Path $env:windir -ChildPath "system32\AppLocker\*") -Recurse -Force
        Write-Warning "Cleanup done, don't forget to reboot the device"
    } catch {
        $_.Exception.Message
    }
}
# Build bridge script
if ($BuildBridgeScript.IsPresent) {
    # create script for wmi-bridge based deployment's from fileshare/url
    $scriptBlock = {
        [CmdletBinding()]
        # init
        $Source = $XmlSource
        $namespaceName = "root\cimv2\mdm\dmmap"
        $GroupName = "AppLockerMDM"
        $parentID = "./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/$GroupName"
        $taskName = "runScriptAsSystem"
        $unsAsSystem = $env:USERNAME -match $env:COMPUTERNAME

        # using wmi bridge required system permissions
        if (!$unsAsSystem) {
            # enable manual invokation
            $action = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-NoProfile -ExecutionPolicy bypass -WindowStyle Hidden -File $($MyInvocation.MyCommand.Source)"
            $task = Register-ScheduledTask -User SYSTEM -Action $action -TaskName $taskName -Force
            Start-ScheduledTask -InputObject $task
        } else {
            try {
                $ErrorActionPreference = 'stop'
                Add-Type -AssemblyName System.Web

                # start logging
                Start-Transcript -Path $(Join-Path -Path C:\ -ChildPath "$($MyInvocation.MyCommand.Name).log")

                # validate source
                if ($Source -match '://.*\.xml') {
                    Write-Verbose "Loading xml content from url $Source"
                    [xml]$policyXML = (New-Object System.Net.WebClient).DownloadString($Source)
                } elseif ($Source -match '\\\\.*\.xml|:\\.*\.xml') {
                    Write-Verbose "Loading xml content from smbshare $Source"
                    [xml]$policyXML = Get-Content -Path $Source
                } else {
                    Write-Warning "Could not validate your source, make sure that your source ends with .xml, only url's and smbshare's are supported"
                    break
                }
                $policyXML.AppLockerPolicy.RuleCollection | Where-Object EnforcementMode -NE "NotConfigured" | ForEach-Object {
                    Write-Verbose "Create ruleset for $($_.Type)"
                    Write-Verbose "Processing XML for $($_.Type) : `n$($_.OuterXml)"
                    switch ($_.Type) {
                        Appx { $className = "MDM_AppLocker_ApplicationLaunchRestrictions01_StoreApps03"; break }
                        Dll { $className = "MDM_AppLocker_DLL03"; break }
                        Exe { $className = "MDM_AppLocker_ApplicationLaunchRestrictions01_EXE03"; break }
                        Msi { $className = "MDM_AppLocker_MSI03"; break }
                        Script { $className = "MDM_AppLocker_Script03"; break }
                    }
                    # Check for existing mdm policy's
                    if ($curInstance = Get-CimInstance -Namespace $namespaceName -ClassName $className) {
                        Write-Verbose "MDM policy for $($_.Type) already exists, deleting before reapply"
                        Remove-CimInstance -InputObject $curInstance
                    }
                    $instanceID = $className.Split('_|03', [System.StringSplitOptions]::RemoveEmptyEntries)[-1]
                    $encodeObj = [System.Net.WebUtility]::HtmlEncode($_.OuterXml)
                    New-CimInstance -Namespace $namespaceName `
                        -ClassName $className `
                        -Property @{
                        ParentID   = $parentID
                        InstanceID = $instanceID
                        Policy     = $encodeObj
                    }
                }
            } catch {
                $_.Exception.Message
            } finally {
                # cleanup scheduled task
                Get-ScheduledTask -TaskName $taskName | Unregister-ScheduledTask -Confirm:$false
                Stop-Transcript
            }
        }
    }
    # export scriptblock as standalone script
    $scriptBlock.ToString().Replace( '$XmlSource', "`"$XmlSource`"" ) | Out-File $ScriptPath
}