Creating symbolic links with PowerShell DSC

Background

In an Azure Windows VM you automatically get a temporary disk drive mapped to D: (on Linux it’s mapped to /dev/sdb1). It is temporary because the storage is assigned from the local storage on the physical host. So if your VM is re-deployed (due to host updates, host failures, resizing, etc.), the VM is recreated on a new host and the VM will be assigned a new temporary drive on the new host. The data on the temporary drive is not migrated, but the OS disk is obviously preserved from the vhd in your storage account or managed-disk.

The problem

In this specific scenario, the customer had a 3rd party legacy application that reads and writes from two directories in the D: drive. The directories paths were hard-coded in the application and were a couple of gigabytes in size, so copying them to the temporary drive each time the VMs were deployed would be time and resource consuming.

Choosing a solution

After thorough testing of course, we decided to create two symbolic links from the D: drive to the real directories in the OS disk (where the directories were already present as part of the image). The symbolic-links creation would be accomplished with either the mklink command, or with the New-Item cmdlet in PowerShell 5.x.

Of course there are other methods overcoming this challenge, such as switching the drive letters with a data-disk and moving the PageFile to the other drive letter. But we decided that the symbolic-links approach would be faster and wouldn’t require an additional data-disk, and with it, additional costs.

The implementation

Since the creation of the symbolic-links would need to happen every time the VM is created (and redeployed), we ended up adding a PowerShell DSC extension to the VM in the ARM template and since there were no built-in DSC Resources in the OS, nor in the DSC Resource Kit in the PowerShell gallery that configures symbolic-links, we wrote a (quick-and-dirty) PowerShell module and the resource to create them.

Creating the module structure and the psm1 and schema.mof files is pretty easy when you’re using the cmdlets from the xDSCResourceDesigner module:

Install-Module -Name xDSCResourceDesigner

$ModuleName = 'myModule'
$ResourceName = 'SymbolicLink'
$ModuleFolder = "C:Program FilesWindowsPowerShellModules$ModuleName"

New-xDscResource -Name $ResourceName -Property @(
    New-xDscResourceProperty -Name Path -Type String -Attribute Key
    New-xDscResourceProperty -Name TargetPath -Type String -Attribute Write
) -Path $ModuleFolder

cd $ModuleFolder
New-ModuleManifest -Path ".$ModuleName.psd1"

The contents of the .psm1 resource file C:Program FilesWindowsPowerShellModulesmyModuleDSCResourcesSymbolicLinkSymbolicLink.psm1 should contain the three *-TargetResource functions (Get, Set and Test):

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

    Write-Verbose "Getting SymbolicLink for $Path"

    $Root = Split-Path -Path $Path -Parent
    $LinkName = Split-Path -Path $Path -Leaf
    $TargetPath = $null

    $link = Get-Item -Path (Join-Path -Path $Root -ChildPath $LinkName) -ErrorAction SilentlyContinue
    if($link -and  $link.LinkType -eq 'SymbolicLink') { $TargetPath = $link.Target[0] }
    
    @{Path = $Path; TargetPath = $TargetPath}
}


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

        [System.String]
        $TargetPath
    )

    Write-Verbose "Creating a SymbolicLink from $Path to $TargetPath"

    $Root = Split-Path -Path $Path -Parent
    $LinkName = Split-Path -Path $Path -Leaf
    Set-Location -Path $Root
    New-Item -ItemType SymbolicLink -Name $LinkName -Target $TargetPath | Out-Null
}


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

        [System.String]
        $TargetPath
    )

    Write-Verbose "Testing SymbolicLink for $Path"

    $current = Get-TargetResource -Path $Path
    return (($current.Path -eq $Path) -and ($current.TargetPath -eq $TargetPath))
}

Export-ModuleMember -Function *-TargetResource

And in the configuration document, remember to import the DSC resources from the module:

configuration Main {

    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName myModule

    node localhost {

        SymbolicLink 'INPUT_DIR' {
            Path       = 'D:INPUT_DIR'
            TargetPath = 'C:PathTomyLegacyAppINPUT_DIR'
        }
        
        SymbolicLink 'OUTPUT_DIR' {
            Path       = 'D:OUTPUT_DIR'
            TargetPath = 'C:PathTomyLegacyAppOUTPUT_DIR'
        }
    }
}

Now, to create the zip file containing the configuration document and all required modules:

# Create the zip package
Publish-AzureRmVMDscConfiguration .myDSC.ps1 -OutputArchivePath .myDSC.zip

And upload it to the blob container (used in the ARM template):

# Variables
$storageAccountName = 'statweb'
$resourceGroupName = 'rg-statweb'

# Login to Azure
Login-AzureRmAccount

# Get the Storage Account authentication key
$keys = Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName

# Create a Storage Authentication Context
$context = New-AzureStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $keys.Item(0).value

# Upload the file to the blob container
Set-AzureStorageBlobContent -Context $context -Container dsc -File .myDSC.zip -Blob myDSC.zip

Conclusion

There are usually several methods to accomplish a single task, and you should take under consideration all aspects and constrains, because one can be more effective than another.

And if you don’t already feel comfortable scripting with PowerShell, you should hurry and Start-Learning. There are a ton of excellent resources out there, but if you prefer a face-to-face in-class learning experience, and have a Premier contract, contact your Technical Account Manager (TAM) for more information on our PowerShell Workshop series.

HTH,

Martin.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.