PowerShell.BRE.psm1

#Requires -RunAsAdministrator
#Requires -PSEdition Desktop

$breAssembly = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.RuleEngine")
if (-not $breAssembly)
{
    throw [System.ApplicationException]::new("Failed to load Microsoft.RuleEngine")
}
$extensionsAssebmly = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.BizTalk.RuleEngineExtensions")
if (-not $extensionsAssebmly)
{
    throw [System.ApplicationException]::new("Failed to load Microsoft.RuleEngine")
}

$driver = [Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver]::new()
$ruleStore = $driver.GetRuleStore()

#region Policies
function Clear-Policies
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param ()
    process
    {
        Get-Policy | Remove-Policy -Delete
    }
}

<#
    .SYNOPSIS
        Exports specified policy to file. The policies can be specified explicity or passed via the pipeline. These will be exported by default too the current folder, be can be changed and will be exported in the format Guid_Name.MajorVersion.MinorVersion.xml. FileInfo objects are returned for each exported policy.
    .EXAMPLE
        PS C:\> Export-Policy -Policy $policy
        Exports policy in variable to current folder
    .EXAMPLE
        PS C:\> Export-Policy -Policy $policy -OutPut D:\Temp
        Exports policy in variable to specified folder
    .EXAMPLE
        PS C:\> Get-Policy | Export-Policy
        Gets all BRE policies and exports each to the current folder
    .PARAMETER Policy
        Policy to be exported
    .PARAMETER Output
        Directory for the policy to be exported to. Default is current folder
    #>

function Export-Policy
{
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.RuleEngine.RuleSetInfo]$Policy,
        [Parameter()]
        [System.IO.DirectoryInfo]$Output = "."
    )
    process
    {
        $fileName = "$(New-Guid)_$($Policy.Name).$($Policy.MajorRevision).$($Policy.MinorRevision).xml"
        Write-Debug "FileName = $fileName"
        $filePath = Join-Path -Path $Output -ChildPath $fileName

        if (-not $Output.Exists)
        {
            Write-Verbose "Creating output directory"
            New-Item -Path $Output.FullName -ItemType Directory -Force
        }

        Write-Debug "FilePath = $filePath"
        $driver.ExportRuleSetToFileRuleStore($Policy, $filePath)
        Write-Output ([System.IO.FileInfo]::new($filePath))
    }
}

<#
    .SYNOPSIS
        Searches BRE for policies matching specified parameters. Calling with no paramters will return all BRE polcies, where specifying the name and/or version will filter this list
    .EXAMPLE
        PS C:\> Get-Policy
        Returns all BRE policies
    .EXAMPLE
        PS C:\> Get-Policy -Name Test
        Returns all versions of the BRE policy "Test"
    .EXAMPLE
        PS C:\> Get-Policy -Name -Version 1.0
        Returns version "1.0" of the BRE policy "Test"
    .PARAMETER Name
        Name of the BRE policy to filter on
    .PARAMETER Version
        Version of the BRE policy to filter on
    #>

function Get-Policy
{
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [string]$Name,
        [Parameter()]
        [version]$Version
    )
    process
    {
        if (-not $PSBoundParameters.ContainsKey("Version"))
        {
            Write-Verbose "Looking for policy: $Name"
        }
        else
        {
            Write-Verbose "Looking for policy: $Name v$($version.ToString())"
        }
        $policies = if ($PSBoundParameters.ContainsKey("Name"))
        {
            $ruleStore.GetRuleSets($Name, [Microsoft.RuleEngine.RuleStore+Filter]::All)
        }
        else
        {
            $ruleStore.GetRuleSets([Microsoft.RuleEngine.RuleStore+Filter]::All)
        }
        if ($PSBoundParameters.ContainsKey("Version"))
        {
            $policies = $policies | Where-Object { ($_.MajorRevision -eq $Version.Major) -and ($_.MinorRevision -eq $Version.Minor) }
        }
        Write-Verbose "Found $($policies.Count) policy(s)"
        return $policies
    }
}

<#
    .SYNOPSIS
        Imports and publishes XML BRE policy into the store and optionally deploys the policy. The source XML files can also be cleaned up (removed) once the policy has been imported. Force can also be specified to remove an existing policy of the same name and version as part of the process
    .EXAMPLE
        PS C:\> Import-Policy -Path C:\BREPolicies\Test.1.0.xml
        Imports and publishes the policy "Test" version "1.0" into the BRE store
    .EXAMPLE
        PS C:\> Import-Policy -Path C:\BREPolicies\Test.1.0.xml -Deploy
        Imports, publishes and deploys the policy "Test" version "1.0" into the BRE store
    .EXAMPLE
        PS C:\> Import-Policy -Path C:\BREPolicies\Test.1.0.xml -Deploy -Force
        Imports, publishes and deploys the policy "Test" version "1.0" into the BRE store. If the policy already exists, it is removed to allow the new policy to be imported
    .EXAMPLE
        PS C:\> Import-Policy -Path C:\BREPolicies\Test.1.0.xml -CleanUp
        Imports and publishes the policy "Test" version "1.0" into the BRE store. Once imported, the XML file is removed
    .PARAMETER Path
        Path to the policy XML to be imported
    .PARAMETER Deploy
        Publish and Deploy the policies
    .PARAMETER Force
        Toggle for whether existing policies are to be handled during the import
    .PARAMETER CleanUp
        Toggle whether the source XML is deleted once the import process is complete
    #>

function Import-Policy
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript( { $_.Exists })]
        [System.IO.FileInfo]$Path,
        [Parameter()]
        [switch]$Deploy,
        [Parameter()]
        [switch]$Force,
        [Parameter()]
        [switch]$CleanUp
    )
    process
    {
        Write-Verbose "Reading XML"
        [xml]$policyXml = Get-Content -Path $Path.FullName
        [System.Collections.Generic.List[Microsoft.RuleEngine.RuleSetInfo]]$policies = [System.Collections.Generic.List[Microsoft.RuleEngine.RuleSetInfo]]::new()
        foreach ($p in $policyXml.brl.ruleset)
        {
            Write-Verbose "Processing policy: $($p.name) $($p.version.major).$($p.version.minor)"
            $policies.Add([Microsoft.RuleEngine.RuleSetInfo]::new($p.name, $p.version.major, $p.version.minor))

            $policy = Get-Policy -Name $p.name -Version ([version]::new($p.version.major, $p.version.minor))
            if ($policy)
            {
                Write-Warning "Policy already deployed"
                Write-Debug ($policy | Out-String)
                    
                if ($Force)
                {
                    Remove-Policy -Policy $policy -Delete
                }
            }
        }

        Write-Verbose "Publishing XML policy(s)"
        $driver.ImportAndPublishFileRuleStore($Path.FullName)
        if ($Deploy)
        {
            foreach ($p in $policies)
            {
                Write-Verbose "Deploying policy: $($p.Name) v$($p.MajorRevision).$($p.MinorRevision)"
                Write-Debug ($p | Out-String)
                $driver.Deploy($p)
            }
        }

        if ($CleanUp)
        {
            Write-Verbose "Removing XML"
            Remove-Item -Path $Path.FullName -Force
        }
    }
}

<#
    .SYNOPSIS
        Removed the specified policy from the BRE store. Policy can either be specified explicitly and passed from a pipeline. The default behaviour is just to undeploy the policy, it can also optionally be deleted from the store entirely
    .EXAMPLE
        PS C:\> Remove-Policy -Policy $policy
        Undeploys the specified policy from BRE
    .EXAMPLE
        PS C:\> Remove-Policy -Policy $policy -Delete
        Undeploys and deletes the specified policy from BRE
    .EXAMPLE
        PS C:\> Get-Policy | Remove-Policy
        Undeploys policies from the pipeline
    .PARAMETER Policy
        Policy to be removed
    .PARAMETER Delete
        Use to delete the policy from BRE instead of just undeploying
    #>

function Remove-Policy
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.RuleEngine.RuleSetInfo]$Policy,
        [Parameter()]
        [switch]$Delete
    )
    process
    {
        Write-Verbose "Removing policy: $($Policy.Name) v$($Policy.MajorRevision).$($Policy.MinorRevision)"
        if ($driver.IsRuleSetDeployed($Policy))
        {
            if ($PSCmdlet.ShouldProcess($Policy, "Undeploying policy"))
            {
                $driver.Undeploy($Policy)
                Write-Verbose "Undeployed policy"
            }
        }
        if ($Delete)
        {
            if ($PSCmdlet.ShouldProcess(($Policy | Out-String), "Deleting policy"))
            {
                $ruleStore.Remove($Policy)
                Write-Verbose "Deleted policy"
            }
        }
    }
}
#endregion
    
#region Vocabularies
function Clear-Vocabularies
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param ()        
    process
    {
        if ($PSCmdlet.ShouldProcess())
        {
            Get-Vocabulary | Remove-Vocabulary -Force
        }
    }
}

<#
    .SYNOPSIS
        Exports specified vocabulary to file. The vocabularies can be specified explicity or passed via the pipeline. These will be exported by default too the current folder, be can be changed and will be exported in the format Guid_Name.MajorVersion.MinorVersion.xml. FileInfo objects are returned for each exported vocabulary.
    .EXAMPLE
        PS C:\> Export-Vocabulary -Vocabulary $vocabulary
        Exports vocabulary in variable to current folder
    .EXAMPLE
        PS C:\> Export-Vocabulary -Vocabulary $vocabulary -OutPut D:\Temp
        Exports vocabulary in variable to specified folder
    .EXAMPLE
        PS C:\> Get-Vocabulary | Export-Vocabulary
        Gets all BRE vocabularies and exports each to the current folder
    .PARAMETER Vocabulary
        Vocabulary to be exported
    .PARAMETER Output
        Directory for the vocabulary to be exported to. Default is current folder
    #>

function Export-Vocabulary
{
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.RuleEngine.VocabularyInfo]$Vocabulary,
        [Parameter()]
        [System.IO.DirectoryInfo]$Output = "."
    )
    process
    {
        $fileName = "$(New-Guid)_$($Vocabulary.Name).$($Vocabulary.MajorRevision).$($Vocabulary.MinorRevision).xml"
        Write-Debug "FileName = $fileName"
        $filePath = Join-Path -Path $Output -ChildPath $fileName

        if (-not $Output.Exists)
        {
            Write-Verbose "Creating output directory"
            New-Item -Path $Output.FullName -ItemType Directory -Force
        }

        Write-Debug "FilePath = $filePath"
        $driver.ExportVocabularyToFileRuleStore($Vocabulary, $filePath)
        Write-Output ([System.IO.FileInfo]::new($filePath))
    }
}

<#
    .SYNOPSIS
        Searches BRE for vocabularies matching specified parameters. Calling with no paramters will return all BRE vocaularies, where specifying the name and/or version will filter this list
    .EXAMPLE
        PS C:\> Get-Vocabulary
        Returns all BRE vocabularies
    .EXAMPLE
        PS C:\> Get-Vocabulary -Name Test
        Returns all versions of the BRE vocabulary "Test"
    .EXAMPLE
        PS C:\> Get-Vocabulary -Name -Version 1.0
        Returns version "1.0" of the BRE vocabulary "Test"
    .PARAMETER Name
        Name of the BRE vocabulary to filter on
    .PARAMETER Version
        Version of the BRE vocabulary to filter on
    #>

function Get-Vocabulary
{
    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline = $true)]
        [string]$Name,
        [Parameter()]
        [version]$Version
    )
    process
    {
        $vocabs = if ($PSBoundParameters.ContainsKey("Name"))
        {
            $ruleStore.GetVocabularies($Name, [Microsoft.RuleEngine.RuleStore+Filter]::All)
        } 
        else
        {
            $ruleStore.GetVocabularies([Microsoft.RuleEngine.RuleStore+Filter]::All)
        }
        if ($PSBoundParameters.ContainsKey("Version"))
        {
            $vocabs = $vocabs | Where-Object { ($_.MajorRevision -eq $Version.Major) -and ($_.MinorRevision -eq $Version.Minor) }
        }
        Write-Verbose "Found $($vocabs.Count) vocabularies"
        return $vocabs
    }
}

<#
    .SYNOPSIS
        Imports an exported BRE vocabulary whilst handling pre-exisitng vocabularies as well as policies that reference those.
         
        A list of policies is taken from the XML and used to query the rule store. If vocabularies already exist, a list of dependant policies is retrieved and exported before deleting. Once the dependencies are removed, the XML vocabularies are imported and the dependencies restored.
    .EXAMPLE
        PS C:\> Import-Vocabulary -Path C:\Temp\0d54dc5b-e73e-4936-a751-6df7fb5f39f5_Vocab1.1.0.xml
        Imports specified BRE vocabulary XML
    .EXAMPLE
        PS C:\> Import-Vocabulary -Path C:\BREPolicies\Test.1.0.xml -CleanUp
        Imports and publishes the vocabulary "Test" version "1.0" into the BRE store. Once imported, the XML file is removed
    .EXAMPLE
        PS C:\> Import-Vocabulary -Path C:\BREPolicies\Test.1.0.xml -Force
        Imports and publishes the vocabulary "Test" version "1.0" into the BRE store. Each vocabulary in the XML is looked up in the store. If the vocabulary already exists the dependencies are backed up and restored once the vocabularies have been imported
    .PARAMETER Path
        Path to the vocabulary XML to be imported
    .PARAMETER Force
        Toggle for whether dependent policies are to be handled during the import
    .PARAMETER CleanUp
        Toggle whether the source XML is deleted once the import process is complete
    #>

function Import-Vocabulary
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateScript( { $_.Exists })]
        [System.IO.FileInfo]$Path,
        [Parameter()]
        [switch]$Force,
        [Parameter()]
        [switch]$CleanUp
    )
    process
    {
        Write-Verbose "Reading XML"
        [xml]$vocabXml = Get-Content -Path $Path.FullName
        [System.Collections.Generic.List[Microsoft.RuleEngine.VocabularyInfo]]$vocabs = [System.Collections.Generic.List[Microsoft.RuleEngine.VocabularyInfo]]::new()
        [System.Collections.Generic.Stack[string]]$dependantPolicies = [System.Collections.Generic.List[string]]::new()
        foreach ($v in $vocabXml.brl.vocabulary)
        {
            Write-Verbose "Processing vocabulary: $($v.name) $($v.version.major).$($v.version.minor)"
            $vocabs.Add([Microsoft.RuleEngine.VocabularyInfo]::new($v.name, $v.version.major, $v.version.minor))

            $vocab = Get-Vocabulary -Name $v.name -Version ([version]::new($v.version.major, $v.version.minor))
            if ($vocab)
            {
                Write-Warning "Vocabulary already deployed"
                Write-Debug ($vocab | Out-String)

                Write-Verbose "Checking for dependencies"
                $policies = $ruleStore.GetDependentRuleSets($vocab)
                if ($policies)
                {
                    Write-Verbose "Found $($policies.Count) dependant policies"
                        
                    foreach ($p in $policies)
                    {
                        $policyExport = Export-Policy -Policy $p -Output $env:TEMP
                        $dependantPolicies.Push($policyExport)
                        Remove-Policy -Policy $p -Delete
                    }
                }

                Remove-Vocabulary -Vocabulary $vocab
            }
        }

        Write-Verbose "Publishing XML vocabulary(s)"
        if ($PSCmdlet.ShouldProcess($Path.FullName, "Published vocaulary(s)"))
        {
            $driver.ImportAndPublishFileRuleStore($Path.FullName)
        }

        if ($dependantPolicies.Count -gt 0)
        {
            Write-Verbose "Restoring dependant policy(s)"
            while ($dependantPolicies -gt 0)
            {
                Import-Policy -Path $dependantPolicies.Pop() -Deploy
            }
        }

        if ($CleanUp)
        {
            Write-Verbose "Removing XML"
            Remove-Item -Path $Path.FullName -Force
        }
    }
}


<#
    .SYNOPSIS
        Removed the specified vocabulary from the BRE store. Vocabulary can either be specified explicitly and passed from a pipeline. The default behaviour is just to undeploy the vocabulary, it can also optionally be deleted from the store entirely
    .EXAMPLE
        PS C:\> Remove-Vocabulary -Vocabulary $vocabulary
        Undeploys the specified vocabulary from BRE
    .EXAMPLE
        PS C:\> Remove-Vocabulary -Vocabulary $vocabulary -Delete
        Undeploys and deletes the specified vocabulary from BRE
    .EXAMPLE
        PS C:\> Get-Vocabulary | Remove-Vocabulary
        Undeploys policies from the pipeline
    .PARAMETER Vocabulary
        Vocabulary to be removed
    .PARAMETER Force
        Use to remove all dependent policies to allow the Vocabulary to be removed cleanly
    #>

function Remove-Vocabulary
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.RuleEngine.VocabularyInfo]$Vocabulary,
        [Parameter()]
        [switch]$Force
    )
    process
    {
        $dependantRules = $ruleStore.GetDependentRuleSets($Vocabulary)
        if ($dependantRules.Count -gt 0)
        {
            Write-Warning "Dependant rules found: $($dependantRules.Count)"
            Write-Debug ($dependantRules | Out-String)
            $dependantRules | Remove-Policy -Delete
        }

        if ($PSCmdlet.ShouldProcess(($Vocabulary | Out-String), "Removing vocabulary"))
        {
            $ruleStore.Remove($Vocabulary)
        }
    }
}
#endregion