dsc/Start-OsMofUpload.ps1

function Start-OsMofUpload {
    [cmdletbinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [validatepattern('\A\w\d\Z')]
        [String] $Environment,
        [String] $role,
        [String] $Prefix = "SPZE2",
        [String] $Location = "eastus2",
        [String] $Project,
        [string] $GlobalRG,
        [String] $StorageAccountName,
        [switch] $KeepMof,
        [switch] $WhatIf,
        [string] $ProjectDirectory = (Get-Location | Select-Object -ExpandProperty Path)
    )

    try {
        $GlobalParameters = Get-Content "$ProjectDirectory\global.parameters.json" -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
        $EnvironmentParameters = Get-Content "$ProjectDirectory\env\$Location\$Environment\environment.parameters.json" -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
        $EnvironmentParameters.parameters.Global.value | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | ForEach-Object {
            $GlobalParameters.parameters.Global.value | Add-Member -MemberType NoteProperty -Name $_ -Value $EnvironmentParameters.parameters.Global.value.$_ -Force
        }
    }
    catch {
        throw "Could not generate a valid parameters template and/or import the Project's gobal.parameters.json file! Please check the location!`n$_"
    }

    if (-not $Project) {
        if ($GlobalParameters.parameters.Global.value.AppName) {
            $Project = $GlobalParameters.parameters.Global.value.AppName
        }
        else {
            throw "Could not find the Project's name in global.parameters.json!"
        }
    }

    if (-not $GlobalRG) {
        if ($GlobalParameters.parameters.Global.value.GlobalRG) {
            $GlobalRG = $GlobalParameters.parameters.Global.value.GlobalRG
        }
        else {
            throw "Could not find the GlobalRG parameter in global.parameters.json!"
        }
    }

    if ($StorageAccountName -eq "") {
        if ($GlobalParameters.parameters.Global.value.globalStorageAccount) {
            $StorageAccountName = $GlobalParameters.parameters.Global.value.globalStorageAccount
            if (-not $StorageAccountName) {
                throw "Could not find storage account $StorageAccountName in the currently selected Azure subscription!" 
            }
        }
        else {
            throw "Could not find a value for globalStorageAccount in the global.parameters.json for $Project!`nPlease provide a Storage Account name." 
        }
    }

    if ($project -eq 'atmos' -and $Environment -eq 's1') {
        $ResourceGroupName = 'SPZE2-ATMOS-SBX-S1'
    }
    else {
        $ResourceGroupName = ($Prefix + '-' + $Project + '-' + $Environment).toUpper()
    }

    $AAName = ($Prefix + $Project + $Environment + "OMSAutomation")
    $ConfigurationFile = "$ProjectDirectory\dsc\roles\$role\role.ps1"

    # Retrieves username and password from keyvault for DSC resources that need a domain account to run.
    $StorageAccountKeySource = Get-AzStorageAccountKey -Name $StorageAccountName -ResourceGroupName $GlobalRG | Select-Object -first 1 | ForEach-Object value
    $StorageCred = [PSCredential]::new($StorageAccountName, (ConvertTo-SecureString -String $StorageAccountKeySource -AsPlainText -Force -ErrorAction stop))
    $DomainCred = [PSCredential]::new((Get-AzKeyVaultSecret -VaultName $GlobalParameters.parameters.Global.value.globalKeyvault -Name 'dscaccount' | Select-Object -ExpandProperty SecretValueText), (Get-AzKeyVaultSecret -VaultName $GlobalParameters.parameters.Global.value.globalKeyvault -Name 'dscpassword' | Select-Object -ExpandProperty SecretValue))
    $CertCred = [PSCredential]::new('CertCred', (Get-AzKeyVaultSecret -VaultName $GlobalParameters.parameters.Global.value.globalKeyvault -Name 'certPassword' | Select-Object -ExpandProperty SecretValue))

    Write-Output "Automation Account: $AAName"
    Write-Output "Resource Group: $ResourceGroupName"
    Write-Output "Storage Account: $StorageAccountName"
    Write-Verbose "Role: $role"
    Write-Verbose "Project: $Project"
    Write-Verbose "DomainCred: $DomainCred"
    Write-Verbose "StorageCred: $StorageCred"

    # Import role ConfigData to build the mof file. Environment specific ConfigData will override default role settings on a per environment basis.
    if (Test-Path "$ProjectDirectory\dsc\roles\$role\role.psd1") {
        $RoleCD = Import-PowerShellDataFile -Path "$ProjectDirectory\dsc\roles\$role\role.psd1"
        if (Test-Path "$ProjectDirectory\env\$Location\$Environment\dsc\$role\role.psd1") {
            $EnvRoleCD = Import-PowerShellDataFile -Path "$ProjectDirectory\env\$Location\$Environment\dsc\$role\role.psd1"
            foreach ($key in $EnvRoleCD.AllNodes[0].Keys) {
                $RoleCD.AllNodes[0].$key = $EnvRoleCD.AllNodes[0].$key      
            }
        }
        $RoleCD | ConvertTo-Json -Depth 100 | Out-File "$ProjectDirectory\temp\$role-ConfigData.json"
    }
    else {
        throw "Missing metadata.json or role.psd1 file for DSC role $role! Please create one!`nFile Path: $ProjectDirectory\dsc\roles\$role\"
    }

    # This sets the configuration name to VMSS for the scalesets.
    if ($role -match 'ssapi|sssf|sssps') {
        $ConfigurationName = 'VMSS'
    }
    else {
        $ConfigurationName = $ResourceGroupName
    }

    # Dot sources the DSC configuration, runs it with the specified ConfigData and generates the Mof file.
    $ConfigurationParams = @{ 
        StorageAccountName = $StorageAccountName
        ConfigurationData  = $RoleCD
        ConfigData         = $RoleCD.AllNodes
        StorageCred        = $StorageCred
        DomainCred         = $DomainCred
        CertCred           = $CertCred
        GlobalParameters   = $GlobalParameters
        Environment        = $Environment
        OutputPath         = "$ProjectDirectory\temp\"
        Verbose            = $true
    }

    . $ConfigurationFile
    & $role @ConfigurationParams

    $AutomationParams = @{ 
        AutomationAccountName = $AAName
        ResourceGroupName     = $ResourceGroupName
        ConfigurationName     = $ConfigurationName
        Path                  = "$ProjectDirectory\temp\$role.mof"
        Force                 = $true
        Verbose               = $true
    }

    if (-not $WhatIf) {
        try {
            Import-AzAutomationDscNodeConfiguration @AutomationParams
        }
        catch {
            Write-Error "Failed to upload module to Azure Automation account $AAName!`n$_"
        }
    }
    else {
        $KeepMof = $true
    }

    if ($KeepMof) {
        Write-Warning "$ProjectDirectory\temp\$role.mof file was NOT deleted! Please delete immediately if it contains sensitive information!"
    }
    else {
        Remove-Item "$ProjectDirectory\temp\*.mof" -Force
        if (Test-Path "$ProjectDirectory\temp\*.mof") {
            Remove-Item "$ProjectDirectory\temp\*.mof" -Force
            throw "WARNING! Could not delete generated $ConfigurationName MOF file! Delete immediately! Could contain sensitive information!`nFile Path: $ProjectDirectory\temp\$role.mof" 
        }
    }
}

New-Alias -Name DSCDeploy -Value Start-OsMofUpload -Force
Register-ArgumentCompleter -CommandName Start-OsMofUpload -ParameterName role -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete)
    $foldernames = @()
    $foldernames += Get-ChildItem ((Get-Location | Select-Object -ExpandProperty Path) + "\dsc\roles") -Directory | Select-Object -ExpandProperty Name
    $foldernames | Select-Object -Unique | Sort-Object | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
        "$_"
    }
}