Set-WEMAppLockerRule.ps1

<#
    .Synopsis
    Updates a AppLocker Rule object in the WEM Database.
 
    .Description
    Updates a AppLocker Rule object in the WEM Database.
 
    .Link
    https://msfreaks.wordpress.com
 
    .Parameter IdRule
    ..
 
    .Parameter Name
    ..
 
    .Parameter Description
    ..
 
    .Parameter Permission
    ..
 
    .Parameter IdADObjects
    ..
 
    .Parameter ConditionObject
    ..
 
    .Parameter ExceptionObjects
    ..
 
    .Parameter Connection
    ..
     
    .Example
 
    .Notes
    Author: Arjan Mensch
#>

function Set-WEMAppLockerRule {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)]
        [int]$IdRule,

        [Parameter(Mandatory=$False)]
        [string]$Name,
        [Parameter(Mandatory=$False)]
        [string]$Description = "",
        [Parameter(Mandatory=$False)][ValidateSet("Allow", "Deny")]
        [string]$Permission,
        [Parameter(Mandatory=$False)]
        [int[]]$IdADObjects,
        [Parameter(Mandatory=$False)]
        [pscustomobject]$ConditionObject,
        [Parameter(Mandatory=$False)]
        [pscustomobject[]]$ExceptionObjects,

        [Parameter(Mandatory=$True)]
        [System.Data.SqlClient.SqlConnection]$Connection
    )
    process {

        # grab the orginal rule
        $origObject = Get-WEMAppLockerRule -Connection $Connection -IdRule $IdRule
        $Type = $origObject.CollectionType
        # abort if the rule does not exist
        if (-not $origObject) {
            Write-Error "No rule with id $($IdRule) found in the database"
            break
        }

        # check if the conditionobject parameter is set and has content
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and -not $ConditionObject) {
            Write-Error "A ConditionObject cannot be removed from a rule"
            break
        }

        # check if the conditionobject is actually that
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and -not ($ConditionObject.pstypenames[0] -like "Citrix.WEMSDK.AppLockerRule*Condition")) {
            Write-Error "ConditionObject is not the correct type. Please provide a valid AppLockerRuleConditionObject"
            break
        }

        # check if exceptions are valid for the type of condition that is requested
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ExceptionObjects') -and $ExceptionObjects) {
            if (([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $ConditionObject.pstypenames[0] -ne "Citrix.WEMSDK.AppLockerRulePathCondition") -or ([bool]($MyInvocation.BoundParameters.Keys -notmatch 'ConditionObject') -and $origObject.RuleType -ne "PathCondition")) {
                Write-Error "ExceptionObject(s) are only valid in combination with a PathCondition type"
                break
            } else {
                # check if the exceptionobjects are actually that
                foreach($ExceptionObject in $ExceptionObjects) {
                    if ($ExceptionObject -and -not ($ExceptionObject.pstypenames[0] -like "Citrix.WEMSDK.AppLockerRule*Condition")) {
                        Write-Error "ExceptionObject is not the correct type. Please provide a valid AppLockerRuleConditionObject"
                        break
                    }
                }
            }
        }

        # check if requested type matches with conditionobject
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Executable" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRulePathCondition") {
            if ($ConditionObject.Path.Substring($ConditionObject.Path.Length -2) -ne "\*" -and (@(".exe",".com") -notcontains [System.IO.Path]::GetExtension($ConditionObject.Path.Substring($ConditionObject.Path.LastIndexOf("\") + 1)))) {
                Write-Error "For an Executable rule the Path Condition must be for a .exe or a .com file, or it must be a Path Condition"
                break
            }
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Executable" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRuleHashCondition" -and $ConditionObject.Purpose -ne "Executable") {
            Write-Error "For an Executable rule the Hash Condition purpose must be 'Executable'"
            break
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Windows Installer" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRulePathCondition") {
            if ($ConditionObject.Path.Substring($ConditionObject.Path.Length -2) -ne "\*" -and (@(".msi",".msp",".mst") -notcontains [System.IO.Path]::GetExtension($ConditionObject.Path.Substring($ConditionObject.Path.LastIndexOf("\") + 1)))) {
                Write-Error "For a Windows Installer rule the Path Condition must be for a .msi, a .msp or a .mst file, or it must be a Path Condition"
                break
            }
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Windows Installer" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRuleHashCondition" -and $ConditionObject.Purpose -ne "Windows Installer") {
            Write-Error "For an Windows Installer rule the Hash Condition purpose must be 'Windows Installer'"
            break
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Scripts" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRulePathCondition") {
            if ($ConditionObject.Path.Substring($ConditionObject.Path.Length -2) -ne "\*" -and (@(".ps1",".bat",".cmd",".vbs",".js") -notcontains [System.IO.Path]::GetExtension($ConditionObject.Path.Substring($ConditionObject.Path.LastIndexOf("\") + 1)))) {
                Write-Error "For a Scripts rule the Path Condition must be for a .ps1, a .bat, a .cmd, a .vbs or a .js file, or it must be a Path Condition"
                break
            }
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Scripts" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRuleHashCondition" -and $ConditionObject.Purpose -ne "Scripts") {
            Write-Error "For a Scripts rule the Hash Condition purpose must be 'Scripts'"
            break
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "Packaged" -and $ConditionObject.pstypenames[0] -ne "Citrix.WEMSDK.AppLockerRulePublisherCondition") {
            Write-Error "For an Packaged rule only a Publisher Condition is valid"
            break
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "DLL" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRulePathCondition") {
            if ($ConditionObject.Path.Substring($ConditionObject.Path.Length -2) -ne "\*" -and (@(".dll",".ocx") -notcontains [System.IO.Path]::GetExtension($ConditionObject.Path.Substring($ConditionObject.Path.LastIndexOf("\") + 1)))) {
                Write-Error "For a DLL rule the Path Condition must be for a .dll or a .ocx file, or it must be a Path Condition"
                break
            }
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject') -and $Type -eq "DLL" -and $ConditionObject.pstypenames[0] -eq "Citrix.WEMSDK.AppLockerRuleHashCondition" -and $ConditionObject.Purpose -ne "DLL") {
            Write-Error "For a DLL rule the Hash Condition purpose must be 'DLL'"
            break
        }

        # check if the IdADObjects parameter is set and has content
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'idadobjects') -and -not $IdADObjects) {
            Write-Error "You cannot remove all assignments from a rule"
            break
        }

        # check if the ADObjects exist in the requested configuration
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'idadobjects')) {
            foreach($IdADObject in $IdADObjects) {
                $adObject = Get-WEMADUserObject -Connection $Connection -IdSite $IdSite -IdADObject $IdADObject
                if (-not ($adObject)) {
                    Write-Error "Could not find an ADUserObject for IdADObject $($IdADObject)"
                    break
                }
            }
        }

        # build the query to update the rule
        $updateFields = @()
        $SQLQuery = "UPDATE AppLockerRules SET "
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'name') -and $origObject.Name -ne (ConvertTo-StringEscaped $Name)) {
            $updateFields += "Name = '$(ConvertTo-StringEscaped $Name)'"
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'description') -and $origObject.Description -ne (ConvertTo-StringEscaped $Description)) {
            $updateFields += "Description = '$(ConvertTo-StringEscaped $Description)'"
        }
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'permission') -and $origObject.Permission -ne $Permission) {
            $updateFields += "State = $($tableVUEMApplockerRulePermission[$Permission])"
        }

        # only update if any of the fields were updated
        $isUpdated = $false

        if($updateFields) { 
            $SQLQuery += "{0} " -f ($updateFields -join ", ")
            $SQLQuery += "WHERE IdRule = $($IdRule)"
            Write-Verbose "Query built: $($SQLQuery)"
            $null = Invoke-SQL -Connection $Connection -Query $SQLQuery

            # object is updated
            $isUpdated = $true
        } else {
            Write-Verbose "No parameters to update the AppLockerRule were provided, checking ConditionObject, ExceptionObjects, and IdADObjects"
        }

        # checking ConditionObject
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ConditionObject')) {
            Write-Verbose "ConditionObject parameter was found, process ConditionObject"
            # since the object is valid (this was checked earlier) delete the ConditionObject from the database and create the new one

            # insert condition
            switch ($ConditionObject.pstypenames[0]) {
                "Citrix.WEMSDK.AppLockerRulePathCondition" { 
                    # delete the old condition
                    Write-Verbose "Deleting PathCondition"
                    $SQLQuery = "DELETE FROM AppLockerRulePathConditions WHERE IdRule = $($IdRule) AND IsException = 0"
                    $null = Invoke-SQL -Connection $db -Query $SQLQuery
                    # build the query to insert the Object
                    $SQLQuery = "INSERT INTO AppLockerRulePathConditions (IdRule, Path, IsException, RevisionId, Reserved01) VALUES ($($IdRule),'$($ConditionObject.Path)',0,0,NULL)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery                    
                }
                "Citrix.WEMSDK.AppLockerRulePublisherCondition" {
                    # delete the old condition
                    Write-Verbose "Deleting PpublisherCondition"
                    $SQLQuery = "DELETE FROM AppLockerRulePublisherConditions WHERE IdRule = $($IdRule) AND IsException = 0"
                    $null = Invoke-SQL -Connection $db -Query $SQLQuery
                    # build the query to insert the Object
                    $SQLQuery = "INSERT INTO AppLockerRulePublisherConditions (IdRule, FilePath, FileName, LowSection, HighSection, Product, Publisher, IsException, RevisionId, Reserved01) VALUES ($($IdRule),'$($ConditionObject.FilePath)','$($ConditionObject.FileName)','$($ConditionObject.LowSection)','$($ConditionObject.HighSection)','$($ConditionObject.Product)','$($ConditionObject.Publisher)',0,0,NULL)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                }
                "Citrix.WEMSDK.AppLockerRuleHashCondition" {
                    # remove hashes from the old condition
                    $SQLQuery = "SELECT TOP (1) * FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) AND IsException = 0 ORDER BY IdCondition DESC"
                    $result = Invoke-SQL -Connection $Connection -Query $SQLQuery
                    $vuemAppLockerCondition = $result.Tables.Rows
                    Write-Verbose "Deleting HashCondition hashes"
                    $SQLQuery = "DELETE FROM AppLockerRuleFileHashes WHERE IdCondition = $($vuemAppLockerCondition.IdCondition)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                    Write-Verbose "Deleting HashCondition"
                    $SQLQuery = "DELETE FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) AND IsException = 0"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery

                    # build the query to insert the Object
                    $SQLQuery = "INSERT INTO AppLockerRuleHashConditions (IdRule, IsException, RevisionId, Reserved01) VALUES ($($IdRule),0,0,NULL)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                    
                    # grab the new Object
                    $SQLQuery = "SELECT TOP (1) * FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) ORDER BY IdCondition DESC"
                    $result = Invoke-SQL -Connection $Connection -Query $SQLQuery
                    $vuemAppLockerCondition = $result.Tables.Rows

                    # process hashes
                    foreach($hash in $ConditionObject.Hashes) {
                        $SQLQuery = "INSERT INTO AppLockerRuleFileHashes (IdCondition, HashAlgorithm, Hash, FileLength, RevisionId, Reserved01, FileName) VALUES ($($vuemAppLockerCondition.IdCondition),0,$($hash.Hash),$($hash.FileLength),0,NULL,'$($hash.FileName)')"
                        $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                    }
                }
                Default {}
            }

            # object is updated
            $isUpdated = $true
        }

        # checking ExceptionObjects
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'ExceptionObjects')) {
            Write-Verbose "ExceptionObjects parameter was found, process ExceptionObjects"

            # delete the existing exceptionobjects (this also takes care of $null value)
            Write-Verbose "Deleting PathCondition exceptions and PublisherCondition exceptions"
            $SQLQuery = "DELETE FROM AppLockerRulePathConditions WHERE IdRule = $($IdRule) AND IsException = 1;DELETE FROM AppLockerRulePublisherConditions WHERE IdRule = $($IdRule) AND IsException = 1"
            $null = Invoke-SQL -Connection $db -Query $SQLQuery

            # grab Hash condition exceptions
            $SQLQuery = "SELECT * FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) AND IsException = 1"
            $result = Invoke-SQL -Connection $db -Query $SQLQuery
            foreach($row in $result.Tables.rows) {
                Write-Verbose "Deleting Hashes for the HashCondition exception in condition $($row.IdCondition)"
                $SQLQuery = "DELETE FROM AppLockerRuleFileHashes WHERE IdCondition = $($row.IdCondition)"
                Invoke-SQL -Connection $Connection -Query $SQLQuery
            }

            # delete hash condition exceptions (this is now possible because the hashes are also deleted)
            Write-Verbose "Deleting HashCondition exceptions"
            $SQLQuery = "DELETE FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) AND IsException = 1"
            $null = Invoke-SQL -Connection $db -Query $SQLQuery

            if ($ExceptionObjects) {
                # create any new exceptionobjects
                # insert exceptions
                foreach($ExceptionObject in $ExceptionObjects) {
                    switch ($ExceptionObject.pstypenames[0]) {
                        "Citrix.WEMSDK.AppLockerRulePathCondition" {
                            # build the query to insert the Object
                            $SQLQuery = "INSERT INTO AppLockerRulePathConditions (IdRule, Path, IsException, RevisionId, Reserved01) VALUES ($($IdRule),'$($ExceptionObject.Path)',1,0,NULL)"
                            $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                        }
                        "Citrix.WEMSDK.AppLockerRulePublisherCondition" {
                            # build the query to insert the Object
                            $SQLQuery = "INSERT INTO AppLockerRulePublisherConditions (IdRule, FilePath, FileName, LowSection, HighSection, Product, Publisher, IsException, RevisionId, Reserved01) VALUES ($($IdRule),'$($ExceptionObject.FilePath)','$($ExceptionObject.FileName)','$($ExceptionObject.LowSection)','$($ExceptionObject.HighSection)','$($ExceptionObject.Product)','$($ExceptionObject.Publisher)',1,0,NULL)"
                            $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                        }
                        "Citrix.WEMSDK.AppLockerRuleHashCondition" {
                            # build the query to insert the Object
                            $SQLQuery = "INSERT INTO AppLockerRuleHashConditions (IdRule, IsException, RevisionId, Reserved01) VALUES ($($IdRule),1,0,NULL)"
                            $null = Invoke-SQL -Connection $Connection -Query $SQLQuery

                            # grab the new Object
                            $SQLQuery = "SELECT TOP (1) * FROM AppLockerRuleHashConditions WHERE IdRule = $($IdRule) AND IsException = 1 ORDER BY IdCondition DESC"
                            $result = Invoke-SQL -Connection $Connection -Query $SQLQuery
                            $vuemAppLockerException = $result.Tables.Rows

                            # process hashes
                            foreach($hash in $ExceptionObject.Hashes) {
                                $SQLQuery = "INSERT INTO AppLockerRuleFileHashes (IdCondition, HashAlgorithm, Hash, FileLength, RevisionId, Reserved01, FileName) VALUES ($($vuemAppLockerException.IdCondition),0,$($hash.Hash),$($hash.FileLength),0,NULL,'$($hash.FileName)')"
                                $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
                            }
                        }
                        Default {}
                    }
                }
            }

            # object is updated
            $isUpdated = $true
        }

        # checking Assignments
        if ([bool]($MyInvocation.BoundParameters.Keys -match 'idadobjects')) {
            Write-Verbose "IdADObjects parameter was found, process Assignments"

            # adding new assignments
            foreach($IdADObject in $IdADObject) {
                $isAssigned = $false
                foreach($assignment in $origObject.Assignments) {
                    if ($assignment.IdADObject -eq $IdADObject) { $isAssigned = $true }
                }
                if (-not $isAssigned) {
                    # this is a new assignment: add it to the list in the database
                    Write-Verbose "New Assignment detected"
                    $SQLQuery = "INSERT INTO AppLockerRuleAssignments (IdSite, IdAppLockerRule, IdItem, RevisionId, Reserved01) VALUES ($($origObject.IdSite),$($IdRule),$($IdADObject),1,NULL)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery

                    # object is updated
                    $isUpdated = $true
                }
            }
            # removing redundant assignments
            foreach($assignment in $origObject.Assignments) {
                $isRedundant = $True
                foreach($IdADObject in $IdADObjects) {
                    if ($IdADObject -eq $assignment.IdADObject) { $isRedundant = $false }
                }
                if ($isRedundant) {
                    # this assignment is redundant: remove it from the list in the database
                    Write-Verbose "Redundant Assignment detected"
                    $SQLQuery = "DELETE FROM AppLockerRuleAssignments WHERE IdSite = $($origObject.IdSite) AND IdAppLockerRule = $($IdRule) AND IdItem = $($assignment.IdADObject)"
                    $null = Invoke-SQL -Connection $Connection -Query $SQLQuery

                    # object is updated
                    $isUpdated = $true
                }
            }
        }

        if ($isUpdated) {
            # object was updated so increase its version
            $SQLQuery = "UPDATE AppLockerRules SET RevisionId = $($origObject.Version + 1) WHERE IdRule = $($IdRule)"
            $null = Invoke-SQL -Connection $Connection -Query $SQLQuery
        }

        # grab the updated object
        $updatedObject = Get-WEMAppLockerRule -Connection $Connection -IdRule $IdRule

        # Updating the ChangeLog
        New-ChangesLogEntry -Connection $Connection -IdSite $origObject.IdSite -IdElement $IdRule -ChangeType "Update" -ObjectName $updatedObject.Name -ObjectType "AppLocker Rule\$($tableVUEMAppLockerChangeLogType["$($tableVUEMAppLockerCollectionType[$updatedObject.CollectionType]).$($tableVUEMAppLockerRuleType[$updatedObject.RuleType])"])" -NewValue "N/A" -ChangeDescription $null -Reserved01 $null
        
    }
}