AxAzureBlueprint.psm1

function Connect-AzureBlueprint {
    [CmdletBinding()]
    param (
        [parameter(mandatory=$true)]
        [string]$ManagementGroupName,
        [switch]$Force
    )
    
    begin {
        $Script:AzureContext = Get-AzContext
        if (!$Script:AzureContext){
            Login-AzAccount
            $Script:AzureContext = Get-AzContext
        }
        if (!$Script:AzureContext){
            Write-Warning "Could not connect to Azure"
            Continue
        }
    }
    
    process {
        $ManagementGroups = Get-AzManagementGroup
        if ($ManagementGroups.Name -notcontains $ManagementGroupName){
            if ($Force){
                New-AzManagementGroup -GroupName $ManagementGroupName
            } else {
                Write-Warning "$ManagementGroupName not found. Use the Force switch if you want to create it"
                continue
            }
        }
        $Script:ManagementGroupName = $ManagementGroupName
        $Script:AzureProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
        $Script:AzureProfileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($Script:AzureProfile)
        $Script:BlueprintPrefix = 'https://management.azure.com/providers/Microsoft.Management/managementGroups/{0}/providers/Microsoft.Blueprint/blueprints' -f $Script:ManagementGroupName
        $Script:APIversion = '?api-version=2017-11-11-preview'
        Write-Verbose "Connected to $ManagementGroupName"
    }
    
    end {
    }
}
function Get-AzureBlueprint {
    [CmdletBinding()]
    param (
        [Parameter (ParameterSetName = 'Specific', Mandatory = $True)]
        [string]$Blueprint,
        [Parameter (ParameterSetName = 'All')]
        [Switch]$ListAll,
        [switch]$AsObject
    )
    
    begin {
        if (!$Script:ManagementGroupName){Connect-AzureBlueprint}
        Get-Header
        $ParamHash = @{
            Uri = ''
            Method = 'Get'
            Headers = $Script:Header
            UseBasicParsing = $True
        }
    }
    
    process {
        if ($ListAll){
            $ParamHash.Uri = '{0}{2}' -f $Script:BlueprintPrefix,$Blueprint,$Script:APIversion
        } else {
            $ParamHash.Uri = '{0}/{1}{2}' -f $Script:BlueprintPrefix,$Blueprint,$Script:APIversion
        }
        try {
            $Blueprint = Invoke-WebRequest @ParamHash | Select-Object -ExpandProperty Content
        } catch {
            Write-Warning "$Blueprint not found!"
            continue
        }
    }
    
    end {
        if ($AsObject){
            $Blueprint | ConvertFrom-Json
        } else {
            $Blueprint
        }
    }
}
function Get-AzureBlueprintArtifact {
    [CmdletBinding()]
    param (
        [string]$Blueprint,
        [string[]]$Artifact,
        [switch]$ListAllArtifacts,
        [switch]$AsObject
    )
    
    begin {
        if (!$Script:ManagementGroupName){Connect-AzureBlueprint}
        Get-Header
        $ParamHash = @{
            Uri = ''
            Method = 'GET'
            Headers = $Script:Header
            UseBasicParsing = $True
        }
    }
    
    process {
        $ArtifactJson = @()
        if ($Artifact){
            $ArtifactJson += foreach ($a in $Artifact){
                $ParamHash.Uri = '{0}/{1}/artifacts/{2}{3}' -f $Script:BlueprintPrefix,$Blueprint,$a,$Script:APIversion
                Invoke-WebRequest @ParamHash | Select-Object -ExpandProperty Content
            }
        }
        elseif ($ListAllArtifacts){
            $ParamHash.Uri = '{0}/{1}/artifacts{2}' -f $Script:BlueprintPrefix,$Blueprint,$Script:APIversion
            $ArtifactJson += Invoke-WebRequest @ParamHash| Select-Object -ExpandProperty Content
        }
        else {
            Write-warning "Please provide specific artifact names or the -ListAllArtifacts switch"
            continue
        }
    }
    
    end {
        if ($AsObject){
            if ($ListAllArtifacts){
                $ArtifactJson | ConvertFrom-Json | Select-Object -ExpandProperty value
            } else {
                $ArtifactJson | ConvertFrom-Json
            }
        }
        else {$ArtifactJson}
    }
}
function Get-AzureBlueprintImportParameters {
    [CmdletBinding()]
    param (
        [ValidateScript( {$_.exists})]
        [System.IO.DirectoryInfo]$ARMTemplateDirectory,
        [switch]$UseFilenameAsArtifactname
    )
    
    begin {
    }
    
    process {
        $Files = Get-ChildItem $ARMTemplateDirectory -filter '*json'
        $Objects = foreach ($File in $Files){
            if (!$UseFilenameAsArtifactname){
                $ArtifactName = Read-Host -Prompt "ArtifactName for $($File.name)"
            } else {
                $ArtifactName = $File.Basename
            }
            $ResourceGroupName = Read-Host -Prompt "Resource Group name for $($File.name)"
            [PSCustomObject]@{
                ARMTemplateJson = $File.FullName
                ArtifactName = $ArtifactName
                ResourceGroup = $ResourceGroupName
            }
        }
    }
    
    end {
        $Objects
    }
}
function Import-AzureBlueprintArtifact {
    [CmdletBinding(DefaultParameterSetName = 'SingleFile')]
    param (
        [parameter(ValueFromPipelineByPropertyName=$true, ParameterSetName = 'SingleFile')]
        [ValidateScript( {$_.exists})]
        [System.IO.FileInfo]$ARMTemplateJson,
        [parameter(ParameterSetName = 'Directory')]
        [ValidateScript( {$_.exists})]
        [System.IO.DirectoryInfo]$ARMTemplateDirectory,
        [ValidateScript( {$_.exists})]
        [System.Io.DirectoryInfo]$TargetDirectory,
        [parameter(ValueFromPipelineByPropertyName=$true, mandatory = $true)]
        [string]$ResourceGroup,
        [parameter(ValueFromPipelineByPropertyName=$true, ParameterSetName = 'SingleFile', mandatory = $true)]
        [string]$ArtifactName,
        [string]$NewBlueprintName = 'blueprint'
    )
    
    begin {

    }
    
    process {
        if ($PSCmdlet.ParameterSetName -eq 'Directory') {
            $ARMTemplateJsons = Get-ChildItem $ARMTemplateDirectory -Filter '*json'
        }
        else {
            $ARMTemplateJsons = $ARMTemplateJson
        }
        foreach ($ARMTemplateJson in $ARMTemplateJsons) {
            if ($PSCmdlet.ParameterSetName -eq 'Directory') {
                $ArtifactName = $ARMTemplateJson.BaseName
            }
            $JsonObjects = Get-JsonObject -BlueprintFolder $TargetDirectory.FullName
            $ARMTemplate = ConvertFrom-Json -InputObject $(Get-Content $ARMTemplateJson.Fullname -Raw)
            $CurrentParameters = $ARMTemplate.parameters
            if (!$CurrentParameters ) {
                $ARMTemplate
                continue
            }
            $MarkedParameters = Set-ArtifactParameter -ParameterObject $CurrentParameters -ResourceGroup $ResourceGroup
            Write-Verbose "Blueprint parameters has been calculated"

            $Blueprint = $JsonObjects | Where-Object {!$_.kind}
            if ($Blueprint) {
                Write-Verbose "Updating $($Blueprint.Filepath)"
                $Blueprint = Get-JsonObject -BlueprintFile $Blueprint.Filepath
                $Blueprint.JsonObject = Add-BlueprintParameter -BlueprintJsonObject $Blueprint.JsonObject -ArtifactParameters $MarkedParameters -ResourceGroup $ResourceGroup
            }
            else {
                $Blueprintfile = '{0}\blueprint.json' -f $TargetDirectory.FullName
                Write-Warning "No blueprint found - creating $Blueprintfile"
                $Blueprint = [PSCustomObject]@{
                    Filepath   = $Blueprintfile
                    Content    = ''
                    Kind       = $Null
                    BaseName   = $NewBlueprintName 
                    Name       = '{0}.json' -f $NewBlueprintName 
                    Blueprint  = $TargetDirectory.Name
                    JsonObject = New-BlueprintJsonObject -Parameters $MarkedParameters -ResourceGroup $ResourceGroup
                }
            }
            $Blueprint.Content = Convertto-Json -InputObject $Blueprint.JsonObject -Depth 99
            $Blueprint.Content | Out-file $Blueprint.Filepath

            $PairHash = Set-ArtifactParameter -ParameterObject $CurrentParameters -ResourceGroup $ResourceGroup -AsPairHash
            $PropParams = New-Object -TypeName PSCustomObject
            $TemplParams = New-Object -TypeName PSCustomObject
            foreach ($key in $PairHash.Keys) {
                $ParamHash = @{
                    InputObject       = $PropParams
                    NotePropertyName  = $key
                    NotePropertyValue = [PsCustomObject]@{value = "[parameters('{0}')]" -f $PairHash[$key]}
                }
                Add-Member @ParamHash
                $ParamHash = @{
                    InputObject       = $TemplParams
                    NotePropertyName  = $key
                    NotePropertyValue = [PsCustomObject]@{type = $CurrentParameters.$key.type}
                }
                Add-Member @ParamHash
            }

            $ARMTemplate.parameters = $TemplParams
            $Artifact = [PSCustomObject]@{
                kind       = 'template'
                properties = [PSCustomObject]@{
                    template      = $ARMTemplate
                    resourceGroup = $ResourceGroup
                    parameters    = $PropParams
                }
            }
            $ArtifactFile = '{0}\{1}.json' -f $TargetDirectory.FullName, $ArtifactName
            Convertto-Json -InputObject $Artifact -Depth 99 | Out-File $ArtifactFile
        }
    }
    
    end {
    }
}
function Remove-AzureBlueprint {
    [CmdletBinding()]
    param (
        [string]$Blueprint,
        [string[]]$Artifact,
        [switch]$Recurse
    )
    
    begin {
        $Ids = @()
        if ($Recurse){
            $Ids = Get-AzureBlueprint -Blueprint $Blueprint -AsObject | Select-Object -ExpandProperty Id
        }
        else {
            $Ids += Get-AzureBlueprintArtifact -Blueprint $Blueprint -Artifact $Artifact -AsObject | Select-Object -ExpandProperty Id
        }
    }
    
    process {
        foreach ($Id in $Ids){
            Get-Header
            $ParamHash = @{
                Uri = 'https://management.azure.com{0}{1}' -f $Id,$Script:APIversion
                Method = 'DELETE'
                Headers = $Script:Header
                UseBasicParsing = $True
            }
            $Name = $Id -split '/' | Select-Object -last 1
            try {
                $Req = Invoke-WebRequest @ParamHash
                Write-Verbose "$Name has been deleted"
            } catch {
                Write-Warning "$Name could not be delete"
            }

        }
    }
    
    end {
    }
}
function Set-AzureBlueprint {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({$_.exists})]
        [System.IO.DirectoryInfo]$BlueprintFolder,
        [switch]$Passthru
    )
    
    begin {
        if (!$Script:ManagementGroupName){Connect-AzureBlueprint}
    }
    
    process {
        $JsonObjects = Get-JsonObject -BlueprintFolder $BlueprintFolder.FullName
        Get-Header
        foreach ($JsonObject in $($JsonObjects | Sort-Object kind)){
            $Name = '{0}{1}' -f $JsonObject.Blueprint,$(if ($JsonObject.kind){'/artifacts/{0}' -f $JsonObject.BaseName} else {''})
            $ParamHash = @{
                Uri = '{0}/{1}{2}' -f $Script:BlueprintPrefix,$Name,$Script:APIversion
                Method = 'PUT'
                Headers = $Script:Header
                Body = $JsonObject.Content
                UseBasicParsing = $True
            }
            Write-Verbose "Uri: $($ParamHash['Uri'])"
            try {
                $Put = Invoke-WebRequest @ParamHash -ErrorVariable Fail
                if ($Passthru){$Put.Content}
            } catch {
                Write-Warning "Could not set $($JsonObject.Name)"
                if ($Fail.message){
                    $Message = try {
                        $fail.message  | ConvertFrom-Json  | Select-Object -ExpandProperty error | Select-Object -ExpandProperty message | Out-String
                    }  catch {
                        $Fail.message
                    }
                }
                else {
                    $Message = $fail
                }
                Write-Warning $Message
            }
        }
    }
    
    end {
    }
}
function Add-BlueprintParameter {
    [CmdletBinding()]
    param (
        $BlueprintJsonObject,
        $ArtifactParameters,
        $ResourceGroupName
    )
    
    begin {
    }
    
    process {
        $AddMemberParams = @()
        if (-not $BlueprintJsonObject.properties.resourceGroups.$ResourceGroupName) {
            Write-Verbose "Adding ResourceGroup $ResourceGroupName to the blueprint"
            $AddMemberParams += @{
                InputObject       = $BlueprintJsonObject.properties.resourceGroups
                NotePropertyName  = $ResourceGroupName
                NotePropertyValue = @{}
                ErrorAction       = 'Stop'
            }            
        }
        $ArtifactNames = Get-Member -MemberType NoteProperty -InputObject $ArtifactParameters  | Select-Object -expand  Name
        foreach ($ArtifactName in $ArtifactNames) {
            $AddMemberParams += @{
                InputObject       = $BlueprintJsonObject.properties.parameters
                NotePropertyName  = $ArtifactName
                NotePropertyValue = $ArtifactParameters.$ArtifactName
                ErrorAction       = 'Stop'
            }
        }
        foreach ($Set in $AddMemberParams) {
            try {
                Add-Member @Set
            }
            catch {
                Write-Verbose "$($Set.NotePropertyName) is already in the Blueprint"
            
            }
        }
            
    }
    
    end {
        $BlueprintJsonObject
    }
}
function Get-Header {
    [CmdletBinding()]
    param (
        [switch]$Passthru
    )
    
    begin {
    }
    
    process {
        $Token = $Script:AzureProfileClient.AcquireAccessToken($Script:AzureContext.Subscription.TenantId)
        Write-Verbose "Accesstoken: $($Token.AccessToken)"
        $Script:Header = @{
            'Content-Type'='application/json'
            'Authorization'='Bearer ' + $Token.AccessToken
        }
    }
    
    end {
        if ($Passthru){
            $Script:Header
        }
    }
}
function Get-JsonObject {
    [CmdletBinding(DefaultParameterSetName='Directory')]
    param (
        [Parameter(ParameterSetName='Directory')]
        [ValidateScript({$_.exists})]
        [System.IO.DirectoryInfo]$BlueprintFolder,
        [Parameter(ParameterSetName='Files')]
        [ValidateScript({$_.exists})]
        [System.IO.FileInfo[]]$BlueprintFile
    )
    
    begin {
        If ($PSCmdlet.ParameterSetName -eq 'Directory'){
            if ($JsonFiles = Get-ChildItem $BlueprintFolder.Fullname -Filter *.json){
            } else {
                Write-Warning "No json files found in $BlueprintFolder"
            }
        } else {
            $JsonFiles = $BlueprintFile
        }
        Write-Verbose $("Doing these files",$JsonFiles.Name | Out-String)
    }
    
    process {
        foreach ($File in $JsonFiles) {
            $FileContent = Get-Content $File.Fullname -Raw
            try {
                $Object = ConvertFrom-Json -InputObject $FileContent
                [PSCustomObject]@{
                    Filepath = $File.Fullname
                    Content = $FileContent
                    Kind = $Object.kind
                    Name = $File.Name
                    BaseName = $File.BaseName
                    Blueprint = $BlueprintFolder.Name
                    JsonObject = $Object
                }
            } catch {
                Write-Warning "$($File.Fullname) is not a valid JSON"
            }
        }
    }
    
    end {
    }
}
function New-BlueprintJsonObject {
    [CmdletBinding()]
    param (
        $Parameters,
        $Description = 'Autogenerated Blueprint',
        $TargetScope = 'subscription',
        $ResourceGroup
    )
    
    begin {
    }
    
    process {
        [PSCustomObject]@{
            properties = @{
                description    = $Description
                targetScope    = $TargetScope
                parameters = $Parameters
                resourceGroups = @{
                    $ResourceGroup = @{}
                }
            }
        }
    }
    
    end {
    }
}
function Set-ArtifactParameter {
    [CmdletBinding()]
    param (
        [PSCustomObject]$ParameterObject,
        $ResourceGroupName,
        $NameStyle = 'ResourceGroupName_ParameterName',
        $DisplayNameStyle = 'ParameterName (ResourceGroupName)',
        [switch]$AsPairHash
    )
    begin {
        $NameStyle = $NameStyle -replace 'ParameterName', '{0}' -replace 'ResourceGroupName', '{1}'
        $DisplayNameStyle = $DisplayNameStyle -replace 'ParameterName', '{0}' -replace 'ResourceGroupName', '{1}'
        $CurrentNPs = Get-Member -InputObject $ParameterObject -MemberType NoteProperty | Select-Object -ExpandProperty Name
    }
    process {
        if ($AsPairHash) {
            $PairHash = @{}
            foreach ($CurrentNP in $CurrentNPs) {
                $PairHash.Add($CurrentNP, $($NameStyle -f $CurrentNP, $ResourceGroupName))
            }
            $PairHash 
        }
        else {
            $NewObject = New-Object -TypeName PSCustomObject
            foreach ($CurrentNP in $CurrentNPs) {
                $NewNPName = $NameStyle -f $CurrentNP, $ResourceGroupName
                $NewNPdisplayName =  $DisplayNameStyle -f $CurrentNP, $ResourceGroupName
                Add-Member -InputObject $NewObject -NotePropertyName $NewNPName -NotePropertyValue $ParameterObject.$CurrentNP
                If (-not $NewObject.$NewNPName.metadata){
                    Add-Member -InputObject $NewObject.$NewNPName -NotePropertyName metadata -NotePropertyValue @{}
                }
                if (-not $NewObject.$NewNPName.metadata.displayName){
                    Add-Member -InputObject $NewObject.$NewNPName.metadata -NotePropertyName displayName -NotePropertyValue $NewNPdisplayName
                }
            }
            $NewObject
        }
    }
    end {
       
    }
   
}