ComputerManagementDsc icon indicating copy to clipboard operation
ComputerManagementDsc copied to clipboard

VirtualMemory: $pageFileName in Set-Resource block ends up null via Join-Path when setting new page file

Open Get-Ryan opened this issue 6 years ago • 6 comments

Details of the scenario you tried and the problem that is occurring

Hello, please forgive any missteps of mine in common practices around contributing to open source projects. I read through the contributing guidelines, but this is still my first time.

I'm trying to put together a short DSC config to be able to change the drive letter of the temporary storage drive attached to Azure VMs when they're first provisioned. The drive is set as the D: drive letter and has the page file assigned to it by default. So in order to change it, I must clear the page file, change the drive letter, and re-assign the page file. I'm performing this via the Azure DSC VM extension.

During testing, I encountered an error stating the parameter PageFileName could not be processed as it was a null or empty string.

After some further testing with editing my local copy of the VirtualMemory resource code, I found that it is the Join-Path cmdlet that populates the $pageFileName variable in the Set-TargetResource block which is returning a null value for some reason. When I tested a replacement line:

$pageFileName = $driveInfo.Name + 'pagefile.sys'

The resource functioned as expected.

Verbose logs showing the problem

Suggested solution to the issue

The replacement line I mentioned does function, but I do understand the value in using Join-Path. I'm at a loss as to why it would be returning a null value the way it's written. When I tested Join-Path outside of DSC, it functioned as expected using the driveInfo.Name value.

The DSC configuration that is used to reproduce the issue (as detailed as possible)

Configuration AzureDiskPagingConfig
{
    Import-DscResource -ModuleName ComputerManagementDsc
    Import-DscResource -ModuleName StorageDsc
    Import-DscResource -ModuleName PSDscResources

    Node $AllNodes.NodeName
    {
        LocalConfigurationManager
        {
            RebootNodeIfNeeded = $true
            ActionAfterReboot = 'ContinueConfiguration'
        }

        WaitForDisk Disk0
        {
            DiskId = 0
            RetryIntervalSec = 10
            RetryCount = 10
        }

        Disk OSVolume
        {
            DiskId = 0
            DriveLetter = 'C'
            PartitionStyle = 'MBR'
            AllowDestructive = $true
            DependsOn = '[WaitForDisk]Disk0'
        }

        WaitForDisk Disk1
        {
            DiskId = 1
            RetryIntervalSec = 10
            RetryCount = 10
        }

        VirtualMemory ClearPagingSettings
        {
            Type = 'NoPagingFile'
            Drive = $Node.OldDriveLetter
            DependsOn = '[WaitForDisk]Disk1'
        }

        Script TempVolume
        {
            GetScript = {
                @{
                    GetScript  = $GetScript
                    SetScript  = $SetScript
                    TestScript = $TestScript
                    Result     = (Get-Partition -DiskNumber 1).DriveLetter
                }
            }
            SetScript = {
                $partition = Get-WmiObject -Class Win32_Volume -Filter "Label = 'Temporary Storage'"
                Set-WmiInstance -InputObject $partition -Arguments @{DriveLetter = $using:Node.NewDriveLetter; Label = 'Temporary Storage'}
                $partition = Get-WmiObject -Class Win32_Volume -Filter "Label = 'Temporary Storage'"
                Write-Verbose "Set Temporary Storage drive to $($partition.DriveLetter)"
            }
            TestScript = {
                $partition = Get-WmiObject -Class Win32_Volume -Filter "Label = 'Temporary Storage'"
                if (!$partition) { Return $true }
                $testPath = "$($partition.DriveLetter)\DATALOSS_WARNING_README.txt"
                if (!(Test-Path -Path $testPath)) { Return $true }
                Return $false
            }
        }

        WaitForDisk Disk1PostChange
        {
            DiskId = 1
            RetryIntervalSec = 10
            RetryCount = 10
            DependsOn = '[Script]TempVolume'
        }

        VirtualMemory PagingSettings
        {
            Type = 'SystemManagedSize'
            Drive = $Node.NewDriveLetter
            DependsOn = '[WaitForDisk]Disk1PostChange'
        }
    }
}

@{
    AllNodes = @(
        @{
            NodeName = "*"
            OldDriveLetter = "D:"
            NewDriveLetter = "T:"
        },
        @{
            NodeName = 'localhost'
        }
    )
}

The operating system the target node is running

OsName : Microsoft Windows Server 2016 Datacenter OsOperatingSystemSKU : DatacenterServerEdition OsArchitecture : 64-bit WindowsBuildLabEx : 14393.3085.amd64fre.rs1_release.190703-1816 OsLanguage : en-US OsMuiLanguages : {en-US}

Version and build of PowerShell the target node is running

PSVersion 5.1.14393.3053 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.14393.3053 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1

Version of the DSC module that was used ('dev' if using current dev branch)

6.4.0.0 - recently installed via install-module

Get-Ryan avatar Jul 28 '19 02:07 Get-Ryan

Hi @Get-Ryan - thank you for raising this! That is really odd that the Join-Path isn't working where the string concatenation is. Is the $driveInfo object null as this would explain the behavior: You can concatenate a string to a null, but Join-Path doesn't allow that.

PlagueHO avatar Jul 28 '19 07:07 PlagueHO

No, the $driveInfo object populates normally, as that was my initial thought as well. And that would have continued to be an issue even with string concatenation, as I have found the New-PageFile line is critical to the overall success. Without it, the VM enters a strange reboot-loop at the end.

This is why I'm at a loss. All the elements of the Join-Path call seem proper. I even validated the $driveInfo.Name is of type String, so there isn't even the possibility of an object type issue.

Get-Ryan avatar Jul 28 '19 13:07 Get-Ryan

That is really strange. We could make a change to use the concatenation method for the path, but I'm always a bit uncomfortable working around a problem like this- e.g. we risk introducing other bugs/issues.

What is also odd, is that I'd expect this line to fail if $driveInfo was $null as well: https://github.com/PowerShell/ComputerManagementDsc/blob/dev/DSCResources/MSFT_VirtualMemory/MSFT_VirtualMemory.psm1#L151 with a You cannot call a method on a null-valued expression. exception.

Can you do a Get-Member on the $driveInfo and confirm the type and that the name property exists?

PlagueHO avatar Jul 30 '19 06:07 PlagueHO

When I was initially testing, I was inserting numerous verbose messages in the code to test various variables and their properties. The $driveInfo does populate with the correct type and the name property contains the proper drive letter. If it didn't, my proposed concatenation line would also not have worked. Again, hence why I was confused. There is no obvious reason I can see why the Join-Path should return null because all of its required parameters are proper.

The only thing I haven't done yet that I've thought to try is to fully test Join-Path's functionality in a DSC config, i.e. verifying it performs as expected through various types of input styles. That's the only possibility I could think of (albeit being a remote one)

Get-Ryan avatar Jul 30 '19 12:07 Get-Ryan

That does sound like a pretty remote possibility. Still scratching my head over this one.

What I'd normally suggest is we create a failing unit test for this scenario/condition, and then modify the code so the test passes. However, it sounds like we might not be able to create a failing unit test for this. In your experience with this issue, were you able to replicate the problem outside of DSC? If so then we've probably got a good shot at creating a failing unit test.

PlagueHO avatar Aug 07 '19 09:08 PlagueHO

No, the Join-Path worked normally outside of DSC using the same inputs via the [System.IO.DriveInfo] object. This is why I was scratching my head as well. My only remaining thoughts in terms of testing is to first test Join-Path functionality within a simple custom DSC module and local config to verify it works in general in a DSC environment (I don't see why it wouldn't, but I haven't personally tested it), and if that succeeds, then see if the same test fails while using it via the DSC extension on an Azure VM. Might be something specific with that technology. I just haven't found the time as yet.

Get-Ryan avatar Aug 07 '19 12:08 Get-Ryan