BicepDev.psm1

#Region '.\Public\Add-BDOutput.ps1' 0
function Add-BDOutput {
    <#
    .SYNOPSIS
        Add outputs to an arm template created by New-BDFile
    .DESCRIPTION
        Provide Name, Type, Value of the output to have it added.
    .EXAMPLE
        PS C:\> .\appGateway.json | Add-BDOutput -Name 'testString' -Type 'string' -value 'hello'
        Add a string output to appGateway.json
    .EXAMPLE
        PS C:\> $foo.BuiltModule | Add-BDOutput -Name 'testObject' -Type 'object' -value @{'hi'='hello'}
        Add an object output to appGateway.json
    .EXAMPLE
        PS C:\> $foo.BuiltModule | Add-BDOutput -Name 'testArray' -Type 'array' -value @('hi','hello')
        Add an array output to appGateway.json
    .INPUTS
        (New-BDFile).BuiltModule
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, HelpMessage = "The built bicep template returned from New-BDFile")]
        [System.IO.FileInfo]
        $BuiltModule,
        [parameter(Mandatory, HelpMessage = "Name of the output")]
        [string]
        $Name,
        [parameter(Mandatory, HelpMessage = "[type] of the output")]
        [string]
        $Type,
        [parameter(Mandatory, HelpMessage = "Value of the output")]
        $Value
    )
    process {
        $ModuleFileContent = Get-Content $BuiltModule.FullName -Encoding 'utf8' -Raw | ConvertFrom-Json
        $ModuleFileContent.outputs | Add-Member NoteProperty $Name @{type = $type; value = $Value} -Force
        $ModuleFileContent | ConvertTo-Json -Depth 99 | Out-File $BuiltModule.FullName
    }
}
#EndRegion '.\Public\Add-BDOutput.ps1' 39
#Region '.\Public\Convert-BDParam.ps1' 0
function Convert-BDParam {
    <#
    .SYNOPSIS
        Converts parameters in an arm template to ouputs
    .DESCRIPTION
        Either convert all parameters, or those parameters specified in ParametersToConvert, to outputs
    .EXAMPLE
        PS C:\> New-BDFile -BicepDeploymentFile testAppGateway.bicep -BicepModuleFile appGateway.bicep -outvariable $BD
        PS C:\> $BD.BuiltModule | Convert-BDParam
 
        Convert all parameters in $BD.BuiltModule to outputs, with a prefix of 'pars_' as the name.
    .INPUTS
        (New-BDFile).BuiltModule
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, HelpMessage = "The built bicep template returned from New-BDFile")]
        [System.IO.FileInfo]
        $BuiltModule,
        [Parameter(HelpMessage = "Enter names of parameters to convert to outputs")]
        [alias('Params')]
        [string[]]
        $ParametersToConvert
    )
    process {
        $ModuleFileContent = Get-Content $BuiltModule.FullName -Encoding 'utf8' -Raw | ConvertFrom-Json
        $ModuleFileContent.variables | Add-Member 'bdParams' ([pscustomobject]@{}) -Force
        if ($ParametersToConvert) {
           $Parameters = $ModuleFileContent.parameters.psobject.Properties | Where-Object {$_.Name -in $ParametersToConvert}
           Write-Verbose "Found $($Parameters.Count) of $($ParametersToConvert.Count) parameters to convert"
           if ($Parameters.Count -ne $ParametersToConvert.Count) {
            $MissingParams=(($ParametersToConvert | Where-Object {$_ -notin $ModuleFileContent.parameters.psobject.Properties.Name}) -join ', ')
               Write-Warning "Could not find these provided params: $MissingParams"
           }
        }
        else {
            $Parameters = $ModuleFileContent.parameters.psobject.Properties
            Write-Verbose "Found $($Parameters.Name.Count) parameters to convert"
        }
        $Parameters | ForEach-Object {
            $name = $_.name
            $value = "[parameters('{0}')]" -f $name
            $ModuleFileContent.variables.bdParams | Add-Member NoteProperty $name $value -Force
        }
        $ModuleFileContent.outputs | Add-Member NoteProperty "bdParams" @{type = 'object'; value = "[variables('bdParams')]" } -Force
        $ModuleFileContent | ConvertTo-Json -Depth 99 | Out-File $BuiltModule.FullName
    }
}
#EndRegion '.\Public\Convert-BDParam.ps1' 49
#Region '.\Public\Convert-BDVar.ps1' 0
function Convert-BDVar {
    <#
    .SYNOPSIS
        Converts all variables in an arm template to ouputs
    .DESCRIPTION
        This cmdlet adds one output to the $BuiltModule arm template: 'bdVars'
 
        When bicep builds to an ARM template, all copy loop array variables are added to a single variable named 'copy'. We use
        similar logic here to take all of those copy loops inside the copy var, and put them in a new ARM template variable, which
        we use to make a more simple output in bdVars. The pattern follows for all other variables.
    .EXAMPLE
        PS C:\> New-BDFile -BicepDeploymentFile testAppGateway.bicep -BicepModuleFile appGateway.bicep -outvariable $BD
        PS C:\> $BD.BuiltModule | Convert-BDVar
 
        Add all variables in $BD.BuiltModule to new variable (bdVars), and output variable bdVars in the arm template.
    .INPUTS
        (New-BDFile).BuiltModule
    .NOTES
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, HelpMessage = "The built bicep template returned from New-BDFile")]
        [System.IO.FileInfo]
        $BuiltModule,
        [Parameter(HelpMessage = "Enter names of variables to convert to outputs")]
        [alias('Vars')]
        [string[]]
        $VariablesToConvert
    )
    process {
        $ModuleFileContent = Get-Content $BuiltModule.FullName -Encoding 'utf8' -Raw | ConvertFrom-Json
        $ModuleFileContent.variables | Add-Member 'bdVars' ([pscustomobject]@{}) -Force
        if ($VariablesToConvert) {
            foreach ($Variable in $VariablesToConvert) {
                #Check both top level variables and nested vars inside the 'copy' variable
                if ($ModuleFileContent.variables.psobject.properties.name -notcontains $Variable) {
                    if ($ModuleFileContent.variables.copy.name -notcontains $Variable) {
                        Write-Warning "Could not find a variable named $Variable"
                    }
                    else {
                        $ModuleFileContent.variables.bdVars | Add-Member NoteProperty $Variable ("[variables('{0}')]" -f $Variable) -Force
                    }
                }
                else {
                    $ModuleFileContent.variables.bdVars | Add-Member NoteProperty $Variable $ModulefileContent.variables.$Variable -Force
                }

            }
        }
        else {
            $ModuleFileContent.variables.psobject.Properties | ForEach-Object {
                $name = $_.name
                $value = $_.value
                if ($name -notin @('copy','bdVars')) {
                    $ModuleFileContent.variables.bdVars | Add-Member NoteProperty $name $PSItem.Value -Force
                }
                elseif ($name -eq 'copy') {
                    foreach ($copyArray in $value) {
                        $ModuleFileContent.variables.bdVars | Add-Member NoteProperty $copyArray.Name ("[variables('{0}')]" -f $copyArray.name) -Force
                    }
                }
            }
        }
        $ModuleFileContent.outputs | Add-Member NoteProperty "bdVars" @{type = 'object'; value = "[variables('bdVars')]"} -Force
        $ModuleFileContent | ConvertTo-Json -Depth 99 | Out-File $BuiltModule.FullName
    }
}
#EndRegion '.\Public\Convert-BDVar.ps1' 68
#Region '.\Public\Get-BDOutput.ps1' 0
function Get-BDOutput {
    <#
    .SYNOPSIS
        Convert JObject, returned from 'Outputs' property of New-AzResourceGroupDeployment, to PSCustomObject.
    .DESCRIPTION
        This is a simple quality of life feature, since the output of New-AzResourceGroupDeployment isn't readily usable in PS
    .EXAMPLE
        PS C:\> New-BDFile -BicepDeploymentFile testAppGateway.bicep -BicepModuleFile appGateway.bicep -outvariable $BD
        PS C:\> New-AzResourceGroupDeployment -ResourceGroupName 'rg-bdfiles' -TemplateFile $BD.BuiltModule | Get-BDOutput -OutputName $BD.OutputName
 
        Grabs the appGateway output property and converts it from jqlinq to PSCustomObject
    .INPUTS
        [PSResourceGroupDeployment]
    .OUTPUTS
        [PsCustomObject]
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, HelpMessage = "The output of New-AzResourceGroupDeployment")]
        [Object]
        $InputObject,
        [Parameter(Mandatory, HelpMessage = "The name of the output variable to return")]
        [string]
        $OutputName
    )
    process {
        $InputObject.outputs.$OutputName.Value.ToString() | ConvertFrom-Json
    }
}
#EndRegion '.\Public\Get-BDOutput.ps1' 30
#Region '.\Public\New-BDFile.ps1' 0
function New-BDFile {
    <#
    .SYNOPSIS
        Creates copies of bicep files to be used for testing with this module.
    .DESCRIPTION
        Given a bicep module that you want to develop/test with, and a bicep template file that calls that module,
        this cmdlet should copy both files, build your template, and output an ARM template ready for use by
        the other cmdlets in this module.
    .EXAMPLE
        PS C:\> New-BDFile -BicepDeploymentFile testAppGateway.bicep -BicepModuleFile appGateway.bicep -outvariable $BD
        PS C:\> $BD | fl
                BicepDeploymentFile : appGateway\ec03_test.bicep
                BicepModuleFile : appGateway\ec03_appGatewayMicrosoft.bicep
                BuiltModule : appGateway\ec03_appGatewayMicrosoft.json
                OutputProperty : appGateway
 
        Returns a PSObject of files to be used with the other cmdlets in the module
    .OUTPUTS
        [PSCustomObject]
    .NOTES
        TODO Make {$BicepDepFileContent -replace "'.*$ReplaceString'", "'$($NewBicModFile.Name)'"} handle more than 0 path levels
        TODO Write New-BDDeployment controller function
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory, HelpMessage = "The .bicep file that calls the bicep module we want to transform")]
        [ValidateScript( { Test-Path $_ })]
        [Alias('Deploy')]
        [string]
        $BicepDeploymentFile,
        [Parameter(Mandatory, HelpMessage = "The .bicep module we want to transform")]
        [ValidateScript( { Test-Path $_ })]
        [Alias('Module')]
        [string]
        $BicepModuleFile,
        [Parameter(HelpMessage = "Prevent this function from clearing out the resources array from the file we will be injecting output into")]
        [switch]
        $KeepResources
    )
    #Create a new guid and copy the bicep files so we don't go mucking around in the original bicep files
    $GUID = (New-Guid).guid.split('-')[1]
    $NewBicDepName = "$($GUID)_" + (split-path $BicepDeploymentFile -leaf)
    $NewBicModName = "$($GUID)_" + (split-path $BicepModuleFile -leaf)
    $NewBicDepFile = Copy-Item -Path $BicepDeploymentFile -Destination (Join-Path (Split-Path $BicepDeploymentFile) $NewBicDepName) -PassThru -Force
    $NewBicModFile = Copy-Item -Path $BicepModuleFile -Destination (Join-Path (Split-Path $BicepModuleFile) $NewBicModName) -PassThru -Force

    #Replace path to $BicepModuleFile with $NewBicepModFile in $BicepDeploymentFile and build
    $ReplaceString = Split-Path $BicepModuleFile -Leaf
    $BicepDepFileContent = Get-Content $NewBicDepFile -Encoding 'utf8' -Raw
    $BicepDepFileContent = $BicepDepFileContent -replace "'.*$ReplaceString'", "'$($NewBicModFile.Name)'"
    $BicepDepFileContent | Set-Content $NewBicDepFile -Force

    #If build was successful, update the path to $NewBicepModFile with the json file we built and return object for pipeline
    $BicepBuild = bicep build $($NewBicModFile.FullName)
    $BuiltModule = Get-Item ([Io.Path]::ChangeExtension($NewBicModFile.FullName, ".json")) -ErrorAction Ignore

    if ($BuiltModule) {
        $OutputName = (Get-Content $NewBicDepFile | Where-Object { $_ -match $NewBicModFile.Name }).Split(' ')[1]
        $NewBicepDepFileContent = (Get-Content $NewBicDepFile -Encoding 'utf8' -Raw)
        $NewBicepDepFileContent = $NewBicepDepFileContent -replace "'$($NewBicModFile.Name)'", "'$($BuiltModule.Name)'"
        $NewBicepDepFileContent += "`n output $OutputName object = $OutputName"
        $NewBicepDepFileContent | Set-Content $NewBicDepFile -Force

        if (-not $KeepResources) {
            $BuiltModuleContent = (Get-Content $BuiltModule -Encoding 'utf8' -Raw) | ConvertFrom-Json
            $BuiltModuleContent.resources = @()
            $BuiltModuleContent | ConvertTo-Json -Depth 99 | Set-Content $BuiltModule -Force
        }
        if (-not $BuiltModuleContent.outputs) {
            $BuiltModuleContent | Add-Member NoteProperty outputs ([PSCustomObject]@{}) -PassThru | ConvertTo-Json -Depth 99 | Set-Content $BuiltModule -Force
        }
        [PSCustomObject]@{
            BicepDeploymentFile = $NewBicDepFile
            BicepModuleFile     = $NewBicModFile
            BuiltModule         = $BuiltModule
            OutputName          = $OutputName
        }
    }
    #Remove BD files created if we didn't successfully build a module
    else {
        if (Test-Path $NewBicDepFile) {
            Remove-BDFile -BicepDeploymentFile $NewBicDepFile
        }
        if (Test-Path $NewBicModFile) {
            Remove-BDfile -BicepModuleFile $NewBicModFile
        }
        throw "Failed to build bicep file"
    }
}
#EndRegion '.\Public\New-BDFile.ps1' 90
#Region '.\Public\Remove-BDFile.ps1' 0
function Remove-BDFile {
    <#
    .SYNOPSIS
        Deletes files created by New-BDFile
    .DESCRIPTION
        Either delete all files from current session (Output of New-BDFile) or make a best effort at deleting all .json and .bicep files in the directory provided with RemoveFromDirectory
    .EXAMPLE
        PS C:\> New-BDFile -BicepDeploymentFile testAppGateway.bicep -BicepModuleFile appGateway.bicep -outvariable $BD
        PS C:\> $BD | Remove-BDFile
 
        Returns a PSObject of files to be used with the other cmdlets in the module
    .INPUTS
        (New-BDFile)
    .NOTES
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipelineByPropertyName, HelpMessage = "The .bicep file that calls the bicep module we want to transform", ParameterSetName = 'pipe')]
        [Alias('Deploy')]
        [System.IO.FileInfo]
        $BicepDeploymentFile,
        [Parameter(ValueFromPipelineByPropertyName, HelpMessage = "The .bicep module we want to transform", ParameterSetName = 'pipe')]
        [Alias('Module')]
        [System.IO.FileInfo]
        $BicepModuleFile,
        [Parameter(ValueFromPipelineByPropertyName, HelpMessage = "The .json file that was built from New-BDFile", ParameterSetName = 'pipe')]
        [System.IO.FileInfo]
        $BuiltModule,
        [Parameter(HelpMessage = "Search for and remove files matching files created by New-BDFile", ParameterSetName = 'besteffort')]
        [ValidateScript({Test-Path $_})]
        [string]
        $RemoveFromDirectory
    )
    process {
        if ($RemoveFromDirectory) {
            Write-Verbose "Searching for bicep and json files in $RemoveFromDirectory"
            Get-ChildItem -Path $RemoveFromDirectory -Recurse -File -Include *.bicep,*.json | Where-Object {$_.Name -match "^(\d|[a-z]){4}_"} | Foreach-Object {
                Write-Verbose "Attempting to remove $($_.FullName)"
                $_ | Remove-Item -Force
            }
        }
        else {
            $PSBoundParameters.GetEnumerator().ForEach( {
                    Write-Verbose "Attempting to remove $($_.Value.Name)"
                    $_.Value | Remove-Item -Force
                })
        }
    }
}
#EndRegion '.\Public\Remove-BDFile.ps1' 50