cAzureKeyVault.psm1

enum Ensure {
    Absent
    Present
}

<#
    This resource retrieves an Azure Key Vault secret and saves it to a specified file
#>


[DscResource()]
class AzureKeyVaultSecret {

    <#
        Key Vault secret name to retrieve
    #>

    [DscProperty(Key)]
    [string] $SecretName

    <#
        Whether or not to Base64 decode the contents before serializing to a file
    #>

    [DscProperty()]
    [bool] $Base64Decode = $false

    <#
        Fully qualified path to the file that is expected to be present or absent.
    #>

    [DscProperty(Mandatory)]
    [string] $Path

    <#
        Key Vault secret name to retrieve
    #>

    [DscProperty(Mandatory)]
    [string] $VaultName

    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    <#
        Azure Resource Manager account credentials with GET access to the Key Vault
    #>

    [DscProperty(Mandatory)]
    [PSCredential] $Credential

    <#
        Azure Resource Manager account tenant Id used for logging in
    #>

    [DscProperty(Mandatory)]
    [string] $TenantId
  
    [DscProperty(NotConfigurable)]
    [Nullable[datetime]] $CreationTime

    [DscProperty(NotConfigurable)]
    [string] $FileSize

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [void] Set() {
        $this.VerifyModuleDependencies()
        $fileExists = $this.TestFilePath($this.Path)
        if ($this.Ensure -eq [Ensure]::Present) {
            if (-not $fileExists) {
                try {
                    LoginAzureRmAccount -Credential $this.Credential -TenantId $this.TenantId
                    $keyVaultSecret = GetAzureKeyVaultSecret -VaultName $this.VaultName -Name $this.SecretName
                    if ($this.Base64Decode) {
                        $bytes = [System.Convert]::FromBase64String($keyVaultSecret.SecretValueText)
                        [System.IO.File]::WriteAllBytes($this.Path, $bytes)
                    }
                    else {
                        # Encrypt the secret value. Note that this must be decrypted using the same PsDscRunAsCredential used in this step.
                        Set-Content -Path $this.Path -Value (ConvertTo-SecureString $keyVaultSecret.SecretValueText -AsPlainText -Force | ConvertFrom-SecureString)
                    }
                }
                catch {
                    if ($this.TestFilePath($this.Path)) {
                        Remove-Item -LiteralPath $this.Path -Force
                    }
                    throw $_
                }
            }
        }
        else {
            if ($fileExists) {
                Write-Verbose -Message "Deleting the file $($this.Path)"
                Remove-Item -LiteralPath $this.Path -Force
            }
        }
    }

    [bool] Test() {
        $present = $this.TestFilePath($this.Path)
        if ($this.Ensure -eq [Ensure]::Present) {
            return $present
        }
        else {
            return -not $present
        }
    }

    [AzureKeyVaultSecret] Get() {
        $present = $this.TestFilePath($this.Path)

        if ($present) {
            $file = Get-ChildItem -LiteralPath $this.Path
            $this.CreationTime = $file.CreationTime
            $this.FileSize = $file.Length
            $this.Ensure = [Ensure]::Present
        }
        else {
            $this.CreationTime = $null
            $this.FileSize = $null
            $this.Ensure = [Ensure]::Absent
        }

        return $this 
    }

    <#
        .SYNOPSIS
            Helper function to check if file exists
 
        .PARAMETER location
            Fully qualified path to file
    #>

    [bool] TestFilePath([string] $location) {
        $present = $true
        $item = Get-ChildItem -LiteralPath $location -ErrorAction Ignore
        if ($item -eq $null) {
            $present = $false
        }
        elseif ($item.PSProvider.Name -ne "FileSystem") {
            throw "Path $($location) is not a file path."
        }
        elseif ($item.PSIsContainer) {
            throw "Path $($location) is a directory path."
        }

        return $present
    }

    <#
        .SYNOPSIS
            Helper function to validate dependent modules exist
    #>

    [void] VerifyModuleDependencies() {
        $dependentModules = @(
            "AzureRM.Profile", 
            "AzureRM.KeyVault")
        $this.VerifyModuleDependencies($dependentModules)
    }
        
    [void] VerifyModuleDependencies([string[]]$dependentModules) {
        $dependentModules | % {
            if (-not(Get-Module -Name $_ -ListAvailable -Refresh)) {
                $exception = New-Object System.InvalidOperationException "Please ensure that the $_ Powershell module is installed"
                $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "ModuleNotFound", ObjectNotFound, $null
                throw $errorRecord
            }
        }
    }

}

Function LoginAzureRmAccount([PSCredential]$Credential, [string]$TenantId) {
    Login-AzureRmAccount -ServicePrincipal -Credential $Credential -TenantId $TenantId
}

Function GetAzureKeyVaultSecret([string]$VaultName, [string]$Name) {
    return Get-AzureKeyVaultSecret -VaultName $VaultName -Name $Name
}