DSCResources/cAzureStorage/cAzureStorage.psm1

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountName,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountKey,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountContainer,

        [parameter(Mandatory = $false)]
        [System.String]
        $Blob = $null
    )

    Write-Verbose ("In Get Function")

    $returnValue = @{
    Path = $Path
    StorageAccountName = $StorageAccountName
    StorageAccountContainer = $StorageAccountContainer
    Blob = $Blob
    }

    $returnValue
}


function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountName,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountKey,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountContainer,

        [parameter(Mandatory = $false)]
        [System.String]
        $Blob = $null
    )

        <#
            Function to download blobs from a container.
            It will recurse through all folders in the container and if any blob is newer than the local version then
            it will download the file. It looks at the MD5 hash of the blob in storage and compares to the MD5 hash of the local file
            to determine if it should download.
 
        #>

        function Download-Blobs
        {
        Param (
            [parameter(Mandatory = $true)]
            [System.Object]
            $Blobs
            )

            foreach ($BlobItem in $Blobs) 
            {  
                # If the blob is a directory, recurively download blobs in the directory
                if ($BlobItem.GetType().Name -eq "CloudBlobDirectory")
                {
                    $Blobs = $BlobItem.ListBlobs()
                    Download-Blobs($Blobs)
                }
                if ($BlobItem.GetType().Name -eq "CloudBlockBlob")
                {
                    $BlobFile = $BlobContainer.GetBlobReference($BlobItem.Name)
                    # Create local path to download blob from and remove drive if there is one in the blob path in storage
                    $Dir = Join-Path $Path (Split-Path $BlobItem.Name -NoQualifier)
                    if (!(Test-Path $Dir))
                    {
                        Write-Verbose ("Downloading " + $BlobItem.Name)
                        if (!(Test-Path(Split-Path $Dir -Parent)))
                        {
                           New-Item -ItemType Directory -Force -Path (Split-Path $Dir -Parent) | Write-Verbose
                        }
                        $BlobFile.DownloadToFile($Dir,[System.IO.FileMode]::Create)
                    }
                    else
                    {
                        $MD5Hash = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
                        $LocalHash = [System.Convert]::ToBase64String($MD5Hash.ComputeHash([System.IO.File]::ReadAllBytes(($Dir))))
                        $StorageHash = $BlobItem.Properties.ContentMD5

                        if ($LocalHash -ne $StorageHash) 
                        {
                            Write-Verbose ("Downloading newer file " + $BlobItem.Name)
                            if (!(Test-Path(Split-Path $Dir -Parent)))
                            {
                                New-Item -ItemType Directory -Force -Path (Split-Path $Dir -Parent) | Write-Verbose
                            }
                            $BlobFile.DownloadToFile($Dir,[System.IO.FileMode]::Create)
                        }
                    }
                }
            }
        }


        Write-Verbose "In Set Function"

        try
        {
            $Creds = New-Object Microsoft.WindowsAzure.Storage.Auth.StorageCredentials($StorageAccountName, $StorageAccountKey)
            $CloudStorageAccount = New-Object Microsoft.WindowsAzure.Storage.CloudStorageAccount($Creds, $true)
            $CloudBlobClient = $CloudStorageAccount.CreateCloudBlobClient()

            $BlobContainer = $CloudBlobClient.GetContainerReference($StorageAccountContainer)

            if ($Blob -eq $null -or $Blob -eq "")
            {
                $Blobs = $BlobContainer.ListBlobs()
            }
            else
            {
                $Blobs = $BlobContainer.ListBlobs($Blob)
            }
        
            Download-Blobs($Blobs)
        }
        catch
        {
            Write-Verbose ($_.Exception)
            throw $_
        }
}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Path,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountName,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountKey,

        [parameter(Mandatory = $true)]
        [System.String]
        $StorageAccountContainer,
                
        [parameter(Mandatory = $false)]
        [System.String]
        $Blob = $null
    )

        # Set Test result to $True by default and will set to $False if any files need to be downloaded
        $Global:Result = $True

        <#
            Function to list blobs from a container.
            It will recurse through all folders in the container and if any blob is newer than the local version then
            it will set $Result to $False. It looks at the MD5 hash of the blob in storage and compares to the MD5 hash of the local file
            to determine if it should be downloaded.
 
        #>

        function List-Blobs
        {
        Param (
            [parameter(Mandatory = $true)]
            [System.Object]
            $Blobs
            )

            foreach ($BlobItem in $Blobs) 
            {  
                # If the blob is a directory, recurively download blobs in the directory
                if ($BlobItem.GetType().Name -eq "CloudBlobDirectory")
                {
                    $Blobs = $BlobItem.ListBlobs()
                    List-Blobs($Blobs)
                }
                if ($BlobItem.GetType().Name -eq "CloudBlockBlob")
                {
                    # Create local path to download blob from and remove drive if there is one
                    $Dir = Join-Path $Path (Split-Path $BlobItem.Name -NoQualifier)
                    if (!(Test-Path $Dir))
                    {
                        Write-Verbose ("Need to download " + $BlobItem.Name + " as it does not exist on the local path")
                        $Global:Result = $false
                    }
                    else
                    {
                        $MD5Hash = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
                        $LocalHash = [System.Convert]::ToBase64String($MD5Hash.ComputeHash([System.IO.File]::ReadAllBytes(($Dir))))
                        $StorageHash = $BlobItem.Properties.ContentMD5

                        if ($LocalHash -ne $StorageHash) 
                        {
                            Write-Verbose ("Need to download " + $BlobItem.Name + "as it is different then the local file")
                            $Global:Result = $false
                        }
                    }
                }
            }
        }

        Write-Verbose "In Test Function"
        Write-Verbose ("Storage Account is: " + $StorageAccountName)
        Write-Verbose ("Local Path is: " + $Path)
        Write-Verbose ("Container is: " + $StorageAccountContainer)
        Write-Verbose ("Blob is: " + $Blob)

        try
        {
            # Need to import the module here since the module which has RequiredAssemblies listed does not seem to get called for DSC resources
            # Was calling load from before but I think Import-Module is a little cleaner
            # [System.Reflection.Assembly]::LoadFrom("$env:ProgramFiles\WindowsPowerShell\Modules\cAzureStorage\Microsoft.WindowsAzure.Storage.dll") | Write-Verbose
            Import-Module cAzureStorage
            $Creds = New-Object Microsoft.WindowsAzure.Storage.Auth.StorageCredentials($StorageAccountName, $StorageAccountKey)
            $CloudStorageAccount = New-Object Microsoft.WindowsAzure.Storage.CloudStorageAccount($Creds, $true)
            $CloudBlobClient = $CloudStorageAccount.CreateCloudBlobClient()

            $BlobContainer = $CloudBlobClient.GetContainerReference($StorageAccountContainer)
            if ($Blob -eq $null -or $Blob -eq "")
            {
                $Blobs = $BlobContainer.ListBlobs()
            }
            else
            {
                $Blobs = $BlobContainer.ListBlobs($Blob)
            }

            List-Blobs($Blobs)
        }
        catch
        {
            Write-Verbose ($_.Exception)
            throw $_
        }
    
        $Global:Result
}


Export-ModuleMember -Function *-TargetResource