functions/export-d365securitydetails.ps1


<#
    .SYNOPSIS
        Extract details from a User Interface Security file
         
    .DESCRIPTION
        Extracts and partitions the security details from an User Interface Security file into the same structure as AOT security files
         
    .PARAMETER FilePath
        Path to the User Interface Security XML file you want to work against
         
    .PARAMETER OutputDirectory
        Path to the folder where the cmdlet will output and structure the details from the file.
        The cmdlet will create a sub folder named like the input file.
         
        Default value is: "C:\temp\d365fo.tools\security-extraction"
         
    .EXAMPLE
        PS C:\> Export-D365SecurityDetails -FilePath C:\temp\d365fo.tools\SecurityDatabaseCustomizations.xml
         
        This will grab all the details inside the "C:\temp\d365fo.tools\SecurityDatabaseCustomizations.xml" file and extract that into the default path "C:\temp\d365fo.tools\security-extraction"
         
    .NOTES
         
        Tags: Security, Configuration, Permission, Development
         
        Author: Mötz Jensen (@splaxi)
         
        The work and design of this cmdlet is based on the findings by Alex Meyer (@alexmeyer_ITGuy).
         
        He wrote about his findings on his blog:
        https://alexdmeyer.com/2018/09/26/converting-d365fo-user-interface-security-customizations-export-to-aot-security-xml-files/
         
        He published a github repository:
         
        https://github.com/ameyer505/D365FOSecurityConverter
         
        All credits goes to Alex Meyer
#>

function Export-D365SecurityDetails {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Alias('Path')]
        [string]$FilePath,

        [Parameter(Mandatory = $false)]
        [Alias('Output')]
        [string]$OutputDirectory = "C:\temp\d365fo.tools\security-extraction"
    )
    
    begin { }
    
    process {

        if (-not (Test-PathExists -Path $FilePath -Type Leaf)) { return }
        if (-not (Test-PathExists -Path $OutputDirectory -Type Container)) { return }

        [xml] $xdoc = Get-Content $FilePath
        
        $fileName = [System.IO.Path]::GetFileNameWithoutExtension($FilePath)
        
        $OutputDirectory = Join-Path $OutputDirectory $fileName

        Write-PSFMessage -Level Verbose -Message "Creating the output directory for the extraction" -Target $OutputDirectory
        $null = New-Item -Path $OutputDirectory -ItemType Directory -Force -ErrorAction SilentlyContinue

        Write-PSFMessage -Level Verbose -Message "Getting all the security objects."
        $secObjects = $xdoc.SelectNodes("/*/*/*/*/*[starts-with(name(),'AxSec')]")

        if ($secObjects.Count -gt 0) {

            Write-PSFMessage -Level Verbose -Message "Looping through all the security objects we found"
            foreach ( $secObject in $secObjects) {
                
                $secPath = Join-Path $OutputDirectory $secObject.LocalName
                
                $null = New-Item -Path $secPath -ItemType Directory -Force -ErrorAction SilentlyContinue

                $secObjectName = $secObject.Name
                
                if (-not ([string]::IsNullOrEmpty($secObjectName))) {
                    $filePathOut = Join-Path $secPath $secObjectName
                    $filePathOut += ".xml"

                    Write-PSFMessage -Level Verbose -Message "Generating the output file: $filePathOut" -Target $filePathOut
                    $secObject.OuterXml | Out-File $filePathOut
                }
            }
        }
    }
    
    end {
    }
}