AzPolicyLens.Wiki.Platform.Helper.psm1

<#
============================================================================================================================
AUTHOR: Tao Yang
DATE: 11/09/2025
NAME: AzPolicyLens.Wiki.Platform.Helper.psm1
VERSION: 2.0.0
COMMENT: this nested module contains the Azure DevOps and GitHub Wiki related helper functions for AzPolicyLens.Wiki module
============================================================================================================================
#>

using module ./AzPolicyLens.Wiki.Utility.Helper.psm1
#function to create a .order file for ADO wiki
Function newAdoWikiOrderFile {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, HelpMessage = "The directory of the .order file.")]
    [string]$FileDirectory,

    [Parameter(Mandatory = $true, HelpMessage = "The content.")]
    [string]$content
  )

  #Create the directory if it does not exist
  if (-not (Test-Path -Path $FileDirectory -PathType Container)) {
    New-Item -Path $FileDirectory -ItemType Directory | Out-Null
  }
  #Create the .order file
  $filePath = Join-Path -Path $FileDirectory -ChildPath '.order'
  if (Test-Path -Path $filePath) {
    Write-Verbose "[$(getCurrentUTCString)]: The .order file already exists at '$filePath'. checking if $($content) already exists." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $existingContent = Get-Content -Path $filePath -ErrorAction SilentlyContinue
    if ($existingContent -contains $content) {
      Write-Verbose "[$(getCurrentUTCString)]: The content '$content' already exists in the .order file at '$filePath'. No changes made." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    } else {
      Write-Verbose "[$(getCurrentUTCString)]: The content '$content' does not exist in the .order file at '$filePath'. Appending it." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      Add-Content -Path $filePath -Value $content
    }
  } else {
    Write-Verbose "[$(getCurrentUTCString)]: The .order file does not exist at '$filePath'. Creating a new one." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $content | Out-File -FilePath $filePath -Encoding UTF8 -Force
    Write-Verbose "[$(getCurrentUTCString)]: Created ADO wiki order file at '$filePath'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    return $filePath
  }
}

#function to extract the parent management group, subscription, resource group from the resource Id
function getResourceParent {
  param (
    [Parameter(Mandatory = $true)]
    [string]$ResourceId
  )

  $parent = @{
    ManagementGroup = $null
    Subscription    = $null
    ResourceGroup   = $null
  }
  $mgRegex = '(?im)^\/providers\/microsoft\.management\/managementgroups\/([a-zA-Z0-9][a-zA-Z0-9\-_.()]*[a-zA-Z0-9\-_()])\/\S+'
  $subRegex = '(?im)^\/subscriptions\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})'
  $rgRegex = '(?im)^\/subscriptions\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\/resourcegroups\/([a-zA-Z0-9][a-zA-Z0-9\-_.()]*[a-zA-Z0-9\-_()])\/\S+'
  # Extract the parent information from the resource ID
  if ($ResourceId -match $mgRegex) {
    $parent.ManagementGroup = $matches[1]
  }
  if ($ResourceId -match $subRegex) {
    $parent.Subscription = $matches[1]
  }
  if ($ResourceId -match $rgRegex) {
    $parent.ResourceGroup = $matches[1]
  }
  $parent
}

#function to escape special characters in page titles (file and folder names) for ADO wiki
function encodeAdoWikiPageTitle {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, HelpMessage = "The file name to escape.")]
    [string]$StringToEncode
  )
  # Escape special characters for ADO wiki
  #documentation: https://learn.microsoft.com/en-us/azure/devops/project/wiki/wiki-file-structure?view=azure-devops#special-characters-in-wiki-page-titles
  $encodingMapping = @()
  $encodingMapping += @{ character = ':'; encodedString = '%3A' }
  $encodingMapping += @{ character = '<'; encodedString = '%3C' }
  $encodingMapping += @{ character = '>'; encodedString = '%3E' }
  $encodingMapping += @{ character = '*'; encodedString = '%2A' }
  $encodingMapping += @{ character = '?'; encodedString = '%3F' }
  $encodingMapping += @{ character = '|'; encodedString = '%7C' }
  $encodingMapping += @{ character = '-'; encodedString = '%2D' }
  $encodingMapping += @{ character = '"'; encodedString = '%22' }
  $encodingMapping += @{ character = ' '; encodedString = '-' }

  foreach ($mapping in $encodingMapping) {
    $StringToEncode = $StringToEncode -replace [regex]::Escape($mapping.character), $mapping.encodedString
  }
  # Remove any leading or trailing spaces
  $StringToEncode = $StringToEncode.Trim()
  $StringToEncode
}

#function to escape special characters in page titles (file and folder names) for GitHub wiki
function encodeGithubWikiPageTitle {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, HelpMessage = "The file name to escape.")]
    [string]$StringToEncode
  )
  # Escape special characters for GitHub wiki
  $encodingMapping = @()
  $encodingMapping += @{ character = '-'; encodedString = '_' }
  $encodingMapping += @{ character = '.'; encodedString = '_' }
  $encodingMapping += @{ character = ':'; encodedString = '_' }
  $encodingMapping += @{ character = ' '; encodedString = '-' }

  foreach ($mapping in $encodingMapping) {
    $StringToEncode = $StringToEncode -replace [regex]::Escape($mapping.character), $mapping.encodedString
  }
  # Remove any leading or trailing spaces
  $StringToEncode = $StringToEncode.Trim()
  $StringToEncode
}
#function to generate wiki page name
function newGithubWikiPageBaseName {
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true)]
    [string]$resourceName,

    [Parameter(Mandatory = $false)]
    [object]$parent,

    [Parameter(Mandatory = $true)]
    [string]$resourceType
  )
  Switch ($resourceType) {
    'definition' { $BaseName = 'def_' }
    'initiative' { $BaseName = 'init_' }
    'assignment' { $BaseName = 'assign_' }
    'exemption' { $BaseName = 'exemp_' }
    'subscription' { $BaseName = 'sub_' }
    'security_control' { $BaseName = 'sc_' }
    'custom_security_control' { $BaseName = 'csc_' }
    'policy_category' { $BaseName = 'polcat_' }
    default { $BaseName = '' }
  }
  $BaseName = "$BaseName{0}" -f $resourceName
  if ($resourceType -ine 'subscription' -and $resourceType -ine 'security_control' -and $resourceType -ine 'policy_category') {
    if ($parent.managementGroup.length -gt 0) {
      $BaseName = "$BaseName`_{0}" -f $parent.managementGroup
    }
    if ($parent.subscription.length -gt 0) {
      $BaseName = "$BaseName`_{0}" -f $parent.subscription
    }
    if ($parent.resourceGroup.length -gt 0) {
      $BaseName = "$BaseName`_{0}" -f $parent.resourceGroup
    }
  }

  $sanitizedBaseName = encodeGithubWikiPageTitle -stringToEncode $BaseName
  $sanitizedBaseName = $sanitizedBaseName.tolower()
  $sanitizedBaseName
}

#function to build the directory path for the specific policy resource for ADO wiki
function getPolicyResourceAdoDirectoryPath {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, ParameterSetName = 'ResourceId')]
    [string]$ResourceId,

    [Parameter(Mandatory = $true, ParameterSetName = 'ResourceType')]
    [ValidateSet('assignments', 'initiatives', 'definitions', 'exemptions', 'security-controls', 'policy-categories')]
    [string]$ResourceType,

    [Parameter(Mandatory = $false)]
    [bool]$getResourceTypeRoot = $false,

    [Parameter(Mandatory = $false)]
    [string]$BasePath = (Get-Location).Path,

    [parameter(Mandatory = $false, HelpMessage = 'The page style (detailed for engineers or basic for customers).')]
    [ValidateSet('detailed', 'basic')]
    [string]$PageStyle = 'detailed'
  )

  $assignmentResourceIdRegex = '(?im)\/providers\/microsoft\.authorization\/policyassignments\/'
  $initiativeResourceIdRegex = '(?im)\/providers\/microsoft\.authorization\/policysetdefinitions\/'
  $definitionResourceIdRegex = '(?im)\/providers\/microsoft\.authorization\/policydefinitions\/'
  $exemptionResourceIdRegex = '(?im)\/providers\/microsoft\.authorization\/policyexemptions\/'
  $policyMetadataResourceIdRegex = '(?im)\/providers\/microsoft\.policyinsights\/policymetadata\/'
  $managementGroupResourceIdRegex = '(?im)^\/providers\/microsoft\.management\/managementgroups\/'
  $subscriptionResourceIdRegex = '(?im)^\/subscriptions\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
  $resourceGroupResourceIdRegex = '(?im)^\/subscriptions\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\/resourcegroups'
  #determine the resource type based on the resource ID
  if ($PSCmdlet.ParameterSetName -ieq 'ResourceId') {
    if ($ResourceId -match $assignmentResourceIdRegex) {
      $ResourceType = 'assignments'
    } elseif ($ResourceId -match $initiativeResourceIdRegex) {
      $ResourceType = 'initiatives'
    } elseif ($ResourceId -match $definitionResourceIdRegex) {
      $ResourceType = 'definitions'
    } elseif ($ResourceId -match $exemptionResourceIdRegex) {
      $ResourceType = 'exemptions'
    } elseif ($ResourceId -match $policyMetadataResourceIdRegex) {
      $ResourceType = 'security-controls'
    } else {
      Write-Error "[$(getCurrentUTCString)]: Unable to determine the policy resource type from the resource ID: '$ResourceId'."
      return $null
    }
  } else {
    switch ($ResourceType.ToLower()) {
      'assignments' { $ResourceType = 'assignments' }
      'initiatives' { $ResourceType = 'initiatives' }
      'definitions' { $ResourceType = 'definitions' }
      'exemptions' { $ResourceType = 'exemptions' }
      'security-controls' { $ResourceType = 'security-controls' }
      'policy-categories' { $ResourceType = 'policy-categories' }
      default {
        Write-Error "[$(getCurrentUTCString)]: Invalid resource type specified: '$ResourceType'. Valid types are 'assignments', 'initiatives', 'definitions', 'exemptions', 'security-controls', 'policy-categories'."
        return $null
      }
    }
  }

  Write-Verbose "[$(getCurrentUTCString)]: Resource type is '$ResourceType'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  if (!$getResourceTypeRoot) {
    # Normalise ResourceId: remove any whitespace (spaces, tabs, newlines)
    $ResourceId = $ResourceId -replace '\s+', ''
    #determine the parent resource based on the resource ID
    $parentResourceId = $ResourceId -replace '\/providers\/microsoft\.authorization\/policy(assignments|setdefinitions|definitions|exemptions)\/\S+', ''
    if ($parentResourceId.length -gt 0) {
      $parentResourceName = $parentResourceId.split('/')[-1]
      Write-Verbose "[$(getCurrentUTCString)]: Parent resource ID is '$parentResourceId'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      Write-Verbose "[$(getCurrentUTCString)]: Parent resource Name is '$parentResourceName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
    # Get parent resource type
    if ($parentResourceId -match $managementGroupResourceIdRegex) {
      $parentResourceType = 'managementGroups'
    } elseif ($parentResourceId -match $subscriptionResourceIdRegex) {
      $parentResourceType = 'subscriptions'
    } elseif ($parentResourceId -match $resourceGroupResourceIdRegex) {
      $parentResourceType = 'resourceGroups'
      $subscriptionId = $parentResourceId -replace '^\/subscriptions\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/resourcegroups\/\S+', '$1'
    } elseif ($parentResourceId.length -eq 0) {
      $parentResourceType = 'builtIn'
    } else {
      Write-Error "[$(getCurrentUTCString)]: Unable to determine the parent resource type from the resource ID: '$parentResourceId'."
      return $null
    }
    Write-Verbose "[$(getCurrentUTCString)]: Parent resource type is '$parentResourceType'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    # Construct child resource directory path
    if ($parentResourceType -ine 'resourcegroups') {
      $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath $ResourceType.ToUpper() -AdditionalChildPath $parentResourceType, $parentResourceName
    } else {
      $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath $ResourceType.ToUpper() -AdditionalChildPath 'subscriptions', $subscriptionId, $parentResourceName
    }

    Write-Verbose "[$(getCurrentUTCString)]: Child resource directory path is '$childResourceDirectoryPath'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  } else {
    # If we want the root directory for the resource type
    #for basic page style, the security control Markdown pages are stored under the analysis directory
    if ($PageStyle -ieq 'basic' -and $ResourceType -ieq 'security-controls') {
      $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath 'ANALYSIS'
    } else {
      $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath $ResourceType.ToUpper()
    }
    Write-Verbose "[$(getCurrentUTCString)]: Child resource directory path is '$childResourceDirectoryPath'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  }

  $childResourceDirectoryPath
}

#function to build the directory path for the specific resource container (mg, rg, sub) resource for Azure DevOps wiki
function getResourceContainerAdoDirectoryPath {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true)]
    [string]$ResourceId,

    [Parameter(Mandatory = $false)]
    [string]$BasePath = (Get-Location).Path
  )
  $managementGroupResourceIdRegex = '(?im)^\/providers\/microsoft\.management\/managementgroups\/'
  $subscriptionResourceIdRegex = '(?im)^\/subscriptions\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
  $resourceGroupResourceIdRegex = '(?im)^\/subscriptions\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\/resourcegroups'

  # Get parent resource type
  if ($ResourceId -match $managementGroupResourceIdRegex) {
    $resourceType = 'MANAGEMENTGROUPS'
  } elseif ($ResourceId -match $subscriptionResourceIdRegex) {
    $resourceType = 'SUBSCRIPTIONS'
  } elseif ($ResourceId -match $resourceGroupResourceIdRegex) {
    $resourceType = 'RESOURCEGROUPS'
    $subscriptionId = $resourceId -replace '^\/subscriptions\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/resourcegroups\/\S+', '$1'
  } else {
    Write-Error "[$(getCurrentUTCString)]: Unable to determine the resource type from the resource ID: '$resourceId'."
    return $null
  }
  Write-Verbose "[$(getCurrentUTCString)]: Resource type is '$resourceType'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  # Construct child resource directory path
  if ($resourceType -ine 'RESOURCEGROUPS') {
    $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath $ResourceType
  } else {
    $childResourceDirectoryPath = Join-Path -Path $BasePath -ChildPath 'SUBSCRIPTIONS' -AdditionalChildPath $subscriptionId, 'RESOURCEGROUPS'
  }

  Write-Verbose "[$(getCurrentUTCString)]: Child resource directory path is '$childResourceDirectoryPath'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $childResourceDirectoryPath
}

#function to store mapping of wiki pages file name, resource name, resource type, etc.
#This is used to create a lookup table for the wiki pages.
function newWikiPageMapping {
  [OutputType([Hashtable])]
  [CmdletBinding()]
  param (
    [Parameter(Mandatory = $true)]
    [object]$discoveryData,

    [parameter(Mandatory = $true, HelpMessage = 'The output base directory.')]
    [ValidateScript({ Test-Path $_ -PathType 'Container' })]
    [string]$BaseOutputPath,

    [parameter(Mandatory = $false, HelpMessage = 'The title of the wiki.')]
    [ValidateNotNullOrEmpty()]
    [string]$Title = 'POLICY-DOCUMENTATION',

    [parameter(Mandatory = $true, HelpMessage = 'The wiki style. Supported values are "ado" and "github".')]
    [ValidateSet('ado', 'github')]
    [string]$WikiStyle,

    [parameter(Mandatory = $false, HelpMessage = 'The page style (detailed for engineers or basic for customers).')]
    [ValidateSet('detailed', 'basic')]
    [string]$PageStyle = 'detailed'
  )

  $wikiFileMapping = @()
  $adoResourceDirectories = @{
    definition       = 'DEFINITIONS'
    initiative       = 'INITIATIVES'
    assignment       = 'ASSIGNMENTS'
    exemption        = 'EXEMPTIONS'
    subscription     = 'SUBSCRIPTIONS'
    analysis         = 'ANALYSIS'
    policy_category  = 'POLICY CATEGORIES'
    security_control = $PageStyle -ieq 'detailed' ? 'SECURITY CONTROLS' : 'ANALYSIS'
  }
  $config = @{
    BaseOutputPath         = $BaseOutputPath
    WikiStyle              = $WikiStyle
    AdoResourceDirectories = $adoResourceDirectories
  }
  #Process definitions
  $unassignedDefinitions = $discoveryData.unassignedCustomDefinitions
  $builtInDefinitionInUnAssignedCustomInitiative = $discoveryData.builtInDefinitionInUnAssignedCustomInitiative
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.definitions.count) policy definitions." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.definitions) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
      $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'definition'
      $FileDirectory = $BaseOutputPath
    }
    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'definition'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }
  if ($unassignedDefinitions.count -gt 0) {
    #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($unassignedDefinitions.count) unassigned custom policy definitions." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    foreach ($item in $unassignedDefinitions) {
      $parent = getResourceParent -ResourceId $item.Id
      if ($WikiStyle -eq 'ado') {
        $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
        $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
      } else {
        $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'definition'
        $FileDirectory = $BaseOutputPath
      }
      $FileName = '{0}.md' -f $FileBaseName
      #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $mapping = @{
        FileBaseName        = $FileBaseName
        FileName            = $FileName
        FileParentDirectory = $FileDirectory
        FilePath            = Join-Path $FileDirectory $FileName
        ResourceName        = $item.name
        ResourceId          = $item.id
        ResourceType        = 'definition'
        PageType            = 'individual'
        Parent              = $parent
      }
      $wikiFileMapping += $mapping
    }
  }
  if ($builtInDefinitionInUnAssignedCustomInitiative.count -gt 0) {
    #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($builtInDefinitionInUnAssignedCustomInitiative.count) builtIn definition in unassigned custom initiatives." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    foreach ($item in $builtInDefinitionInUnAssignedCustomInitiative) {
      $parent = getResourceParent -ResourceId $item.Id
      if ($WikiStyle -eq 'ado') {
        $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
        $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
      } else {
        $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'definition'
        $FileDirectory = $BaseOutputPath
      }
      $FileName = '{0}.md' -f $FileBaseName
      #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $mapping = @{
        FileBaseName        = $FileBaseName
        FileName            = $FileName
        FileParentDirectory = $FileDirectory
        FilePath            = Join-Path $FileDirectory $FileName
        ResourceName        = $item.name
        ResourceId          = $item.id
        ResourceType        = 'definition'
        PageType            = 'individual'
        Parent              = $parent
      }
      $wikiFileMapping += $mapping
    }
  }
  #Process initiatives

  $unassignedInitiatives = $discoveryData.unassignedCustomInitiatives
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.initiatives.count) policy initiatives." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.initiatives) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
      $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'initiative'
      $FileDirectory = $BaseOutputPath
    }
    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'initiative'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }



  if ($unassignedInitiatives.count -gt 0) {
    #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($unassignedInitiatives.count) unassigned custom policy initiatives." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    foreach ($item in $unassignedInitiatives) {
      $parent = getResourceParent -ResourceId $item.Id
      if ($WikiStyle -eq 'ado') {
        $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
        $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
      } else {
        $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'initiative'
        $FileDirectory = $BaseOutputPath
      }
      $FileName = '{0}.md' -f $FileBaseName
      #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
      $mapping = @{
        FileBaseName        = $FileBaseName
        FileName            = $FileName
        FileParentDirectory = $FileDirectory
        FilePath            = Join-Path $FileDirectory $FileName
        ResourceName        = $item.name
        ResourceId          = $item.id
        ResourceType        = 'initiative'
        PageType            = 'individual'
        Parent              = $parent
      }
      $wikiFileMapping += $mapping
    }
  }
  #Process assignments
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.assignments.count) policy assignments." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.assignments) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
      $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'assignment'
      $FileDirectory = $BaseOutputPath
    }
    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'assignment'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }

  #Process exemptions
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.exemptions.Count) policy exemptions." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.exemptions) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
      $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -PageStyle $PageStyle
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'exemption'
      $FileDirectory = $BaseOutputPath
    }
    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'exemption'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }

  #process policy metadata
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.policyMetadata.Count) policy metadata." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.policyMetadata) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $item.name
      $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath -getResourceTypeRoot $true -PageStyle $PageStyle
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.name -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'security_control'
      $FileDirectory = $BaseOutputPath
    }
    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'security_control'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }

  #process custom security controls
  Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page directory for custom security control pages." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

  if ($WikiStyle -eq 'ado') {
    $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceType 'security-controls' -BasePath $BaseOutputPath -getResourceTypeRoot $true -PageStyle $PageStyle
  } else {
    $FileDirectory = $BaseOutputPath
  }
  #$FileName = '{0}.md' -f $FileBaseName
  #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = ''
    FileName            = ''
    FileParentDirectory = $FileDirectory
    FilePath            = ''
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'custom_security_control'
    PageType            = 'individual'
    Parent              = ''
  }
  $wikiFileMapping += $mapping

  #process policy categories
  Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page directory for policy category pages." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

  if ($WikiStyle -eq 'ado') {
    $FileDirectory = getPolicyResourceAdoDirectoryPath -ResourceType 'policy-categories' -BasePath $BaseOutputPath -getResourceTypeRoot $true -PageStyle $PageStyle
  } else {
    $FileDirectory = $BaseOutputPath
  }
  #$FileName = '{0}.md' -f $FileBaseName
  #Write-Verbose " - [$($item.id)]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = ''
    FileName            = ''
    FileParentDirectory = $FileDirectory
    FilePath            = ''
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'policy_category'
    PageType            = 'individual'
    Parent              = ''
  }
  $wikiFileMapping += $mapping

  #Process subscriptions
  #Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for $($discoveryData.subscriptions.Count) Azure subscriptions." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  foreach ($item in $discoveryData.subscriptions) {
    $parent = getResourceParent -ResourceId $item.Id
    if ($WikiStyle -eq 'ado') {
      $FileBaseName = encodeAdoWikiPageTitle -stringToEncode "$($item.name)_$($item.subscriptionId)"
      $FileDirectory = getResourceContainerAdoDirectoryPath -ResourceId $item.id -BasePath $BaseOutputPath
    } else {
      $FileBaseName = newGithubWikiPageBaseName -resourceName $item.subscriptionId -parent $(getResourceParent -ResourceId $item.Id) -resourceType 'subscription'
      $FileDirectory = $BaseOutputPath
    }

    $FileName = '{0}.md' -f $FileBaseName
    #Write-Verbose " - '[$($item.id)]': '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping = @{
      FileBaseName        = $FileBaseName
      FileName            = $FileName
      FileParentDirectory = $FileDirectory
      FilePath            = Join-Path $FileDirectory $FileName
      ResourceName        = $item.name
      ResourceId          = $item.id
      ResourceType        = 'subscription'
      PageType            = 'individual'
      Parent              = $parent
    }
    $wikiFileMapping += $mapping
  }

  #Process summary pages
  Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for policy summary pages." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

  #definition summary page
  $FileBaseName = $adoResourceDirectories.definition
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Definition summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'definition'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #initiative summary page
  $FileBaseName = $adoResourceDirectories.initiative
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Initiative summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'initiative'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #assignment summary page
  $FileBaseName = $adoResourceDirectories.assignment
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Assignment summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'assignment'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #exemption summary page
  $FileBaseName = $adoResourceDirectories.exemption
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Exemption summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'exemption'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #subscription summary page
  $FileBaseName = $adoResourceDirectories.subscription
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Subscription summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'subscription'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #analysis summary page
  $FileBaseName = $adoResourceDirectories.analysis
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Analysis summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'analysis'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #Security Control summary page
  if ($WikiStyle -ieq 'ado') {
    $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $adoResourceDirectories.security_control
  } else {
    $FileBaseName = encodeGithubWikiPageTitle -stringToEncode $adoResourceDirectories.security_control
  }
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Security Control summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'security_control'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  #Policy Category summary page
  if ($WikiStyle -ieq 'ado') {
    $FileBaseName = encodeAdoWikiPageTitle -stringToEncode $adoResourceDirectories.policy_category
  } else {
    $FileBaseName = encodeGithubWikiPageTitle -stringToEncode $adoResourceDirectories.policy_category
  }
  $FileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Policy Category summary page]: '$FileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $FileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $FileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'policy_category'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping
  #main page
  if ($WikiStyle -eq 'ado') {
    #The file name must match the title of the wiki page
    $renderedTitle = $Title -replace ' ', '-'
  } else {
    $renderedTitle = 'Home'
  }

  $FileBaseName = $renderedTitle.toupper()
  $MainFileName = '{0}.md' -f $FileBaseName

  Write-Verbose " - [Main page]: '$MainFileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $FileBaseName
    FileName            = $MainFileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $MainFileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'main'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for GitHub sidebar page." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $GitHubSidebarFileBaseName = '_Sidebar'
  $GitHubSidebarFileName = '{0}.md' -f $GitHubSidebarFileBaseName
  Write-Verbose " - [GitHub Sidebar page]: '$GitHubSidebarFileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $GitHubSidebarFileBaseName
    FileName            = $GitHubSidebarFileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $GitHubSidebarFileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'github_sidebar'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  Write-Verbose "[$(getCurrentUTCString)]: Generate wiki page names for GitHub footer page." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $GitHubFooterFileBaseName = '_Footer'
  $GitHubFooterFileName = '{0}.md' -f $GitHubFooterFileBaseName
  Write-Verbose " - [GitHub Footer page]: '$GitHubFooterFileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
  $mapping = @{
    FileBaseName        = $GitHubFooterFileBaseName
    FileName            = $GitHubFooterFileName
    FileParentDirectory = $BaseOutputPath
    FilePath            = Join-Path $BaseOutputPath $GitHubFooterFileName
    ResourceName        = ''
    ResourceId          = ''
    ResourceType        = 'github_footer'
    PageType            = 'summary'
    Parent              = $null
  }
  $wikiFileMapping += $mapping

  $config.Add('FileMapping', $wikiFileMapping)
  $config
}

#Function to retrieve file name from the generated page file name mapping
function getWikiPageFileName {
  [CmdletBinding()]
  [OutputType([string])]
  param (
    [Parameter(Mandatory = $true, ParameterSetName = 'individual_azure_resource')]
    [string]$ResourceId,

    [Parameter(Mandatory = $true, ParameterSetName = 'custom_security_control')]
    [string]$CustomSecurityControlId,

    [Parameter(Mandatory = $true, ParameterSetName = 'custom_security_control')]
    [string]$SecurityControlFramework,

    [Parameter(Mandatory = $true, ParameterSetName = 'policy_category')]
    [string]$PolicyCategory,

    [Parameter(Mandatory = $true, ParameterSetName = 'summary')]
    [ValidateSet('definition', 'initiative', 'assignment', 'security_control', 'exemption', 'subscription', 'policy_category', 'analysis', 'main', 'github_sidebar', 'github_footer')]
    [string]$summaryPageType,

    [Parameter(Mandatory = $true)]
    [hashtable]$wikiFileMapping
  )
  # Build hashtable indexes on first use for O(1) lookups
  if (-not $wikiFileMapping._resourceIdIndex) {
    $wikiFileMapping._resourceIdIndex = @{}
    $wikiFileMapping._summaryIndex = @{}
    foreach ($m in $wikiFileMapping.FileMapping) {
      if ($m.ResourceId) {
        $wikiFileMapping._resourceIdIndex[$m.ResourceId.ToLower()] = $m
      }
      if ($m.PageType -eq 'summary') {
        $wikiFileMapping._summaryIndex[$m.ResourceType] = $m
      }
    }
  }
  if ($PSCmdlet.ParameterSetName -eq 'summary') {
    $mapping = $wikiFileMapping._summaryIndex[$summaryPageType]
    if (!$mapping) {
      Write-Error "No wiki page file name mapping found for $summaryPageType summary page."
    }
  } elseif ($PSCmdlet.ParameterSetName -eq 'individual_azure_resource') {
    $mapping = $wikiFileMapping._resourceIdIndex[$ResourceId.ToLower()]
    if (!$mapping) {
      Write-Error "No wiki page file name mapping found for resource '$ResourceId'."
    }
  } elseif ($PSCmdlet.ParameterSetName -eq 'policy_category') {
    $mapping = $wikiFileMapping.FileMapping | Where-Object { $_.ResourceType -ieq 'policy_category' -and $_.PageType -ieq 'individual' }
    $WikiStyle = $wikiFileMapping.WikiStyle
    if ($WikiStyle -ieq 'ado') {
      $fileBaseName = encodeAdoWikiPageTitle -stringToEncode $PolicyCategory
    } else {
      $fileBaseName = newGithubWikiPageBaseName -resourceName $PolicyCategory -parent '' -resourceType 'policy_category'
    }
    $fileName = "{0}.md" -f $fileBaseName
    Write-Verbose "[$(getCurrentUTCString)]: Generated file name for policy category '$PolicyCategory' is '$fileName'." -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    $mapping.FileBaseName = $fileBaseName
    $mapping.FileName = $fileName
    $mapping.FilePath = join-path $mapping.FileParentDirectory $fileName
    $mapping.ResourceName = $PolicyCategory
    $mapping.ResourceId = $PolicyCategory
  } else {
    $mapping = $wikiFileMapping.FileMapping | Where-Object { $_.ResourceType -ieq 'custom_security_control' }
    $WikiStyle = $wikiFileMapping.WikiStyle
    $secControlFullName = "$($CustomSecurityControlId)_$($SecurityControlFramework)"
    if ($WikiStyle -ieq 'ado') {
      $fileBaseName = encodeAdoWikiPageTitle -stringToEncode $secControlFullName
    } else {
      $fileBaseName = newGithubWikiPageBaseName -resourceName $secControlFullName -parent '' -resourceType 'custom_security_control'
    }
    $fileName = "{0}.md" -f $fileBaseName
    $mapping.FileBaseName = $fileBaseName
    $mapping.FileName = $fileName
    $mapping.FilePath = join-path $mapping.FileParentDirectory $fileName
    $mapping.ResourceName = $CustomSecurityControlId
    $mapping.ResourceId = $secControlFullName
  }
  $mapping
}