简体   繁体   中英

How do I conditionally execute a resource in Powershell DSC based on a previous resource executing?

Say I've got two blocks like this:

Configuration TestConfig
{
    Node ('localhost') {
        File foo {
            DestinationPath = 'C:\foo.txt'
            Contents = 'Bar'
        }
        Script dothing {
            SetScript = {'Baz' | set-content C:\foo.txt}
            TestScript = {return $false}
            GetScript = {
                return @{
                    GetScript = ''
                    SetScript = ''
                    TestScript = ''
                    Credential = $Credential
                    Result = (Invoke-Expression -Command $TestScript)
                }
            }
            DependsOn = '[File]foo'
        }
    }
}

I built a test that does something like this, but it seems like the Service resource gets executed regardless of the File resource's test output - that is, if it actually did anything to the file.

VERBOSE: [MACHINENAME]: LCM:  [ Start  Set      ]
VERBOSE: [MACHINENAME]: LCM:  [ Start  Resource ]  [[File]foo]
VERBOSE: [MACHINENAME]: LCM:  [ Start  Test     ]  [[File]foo]
VERBOSE: [MACHINENAME]:                            [[File]foo] The destination object was found and no action is required.
VERBOSE: [MACHINENAME]: LCM:  [ End    Test     ]  [[File]foo]  in 0.0040 seconds.
VERBOSE: [MACHINENAME]: LCM:  [ Skip   Set      ]  [[File]foo]
VERBOSE: [MACHINENAME]: LCM:  [ End    Resource ]  [[File]foo]
VERBOSE: [MACHINENAME]: LCM:  [ Start  Resource ]  [[Script]dothing]
VERBOSE: [MACHINENAME]: LCM:  [ Start  Test     ]  [[Script]dothing]
VERBOSE: [MACHINENAME]: LCM:  [ End    Test     ]  [[Script]dothing]  in 0.0050 seconds.
VERBOSE: [MACHINENAME]: LCM:  [ Start  Set      ]  [[Script]dothing]
VERBOSE: [MACHINENAME]: LCM:  [ End    Set      ]  [[Script]dothing]  in 0.0060 seconds.
VERBOSE: [MACHINENAME]: LCM:  [ End    Resource ]  [[Script]dothing]
VERBOSE: [MACHINENAME]: LCM:  [ End    Set      ]
VERBOSE: [MACHINENAME]: LCM:  [ End    Set      ]    in  0.0390 seconds.

Contrived example aside, how can I only have the Script resource execute only if the File resource actually did something?

My use case here is checking to see if a file has changed in a remote location, and if so, copy it to the local machine, then restart a service. I obviously don't want to restart the service if the file hasn't changed, but I can't see a good idempotent way to do that, since the file is being tested using a hash - I'd have to have my service restart step do the same file hash check.

I don't think there's anything natively built into DSC that lets one resource detect whether the "Set-TargetResource" function was actually executed for a different resource in the same configuration run.

However, the following Script resources will do what you want, but they make use of a global script-level variable to communicate so they'll break if you've got more than one service to configure in a single DSC. The ConfigureService Script resource updates the local config file and sets a flag that the RestartService resource reads to check if a change was made. It's a bit dirty, but it works.

Script ConfigureService {
    GetScript  = { return $null; }
    TestScript = {
        # compare remote and local file contents and return 
        # $true if they match, or $false if they're different
        $source = "D:\temp\dsctest\source.txt";
        $target = "D:\temp\dsctest\target.txt";
        $isMatch = [System.IO.File]::ReadAllText($target) -eq [System.IO.File]::ReadAllText($source);
        # set a flag for the RestartService resource to read
        $script:serviceChanged = -not $isMatch;
        write-verbose "isMatch = $isMatch";
        return $isMatch;
    }
    SetScript  = {
        # overwrite the local file
        $source = "D:\temp\dsctest\source.txt";
        $target = "D:\temp\dsctest\target.txt";
        $content = [System.IO.File]::ReadAllText($source);
        write-verbose "overwriting config";
        [System.IO.File]::WriteAllText($target, $content);
    }
}

Script RestartService {
    DependsOn  = "[Script]ConfigureService"
    GetScript  = { return $null; }
    TestScript = {
        write-verbose "serviceChanged = $($script:serviceChanged)";
        return -not $script:serviceChanged;
    }
    SetScript  = {
        # restart the service
        write-verbose "restarting service";
    }
}

Alternatively just roll it all into a single resource. If you then want to re-use this for more than one service you could move it into a fully-fledged ServiceConfiguration custom resource.

Script ConfigureService {
    GetScript  = { return $null; }
    TestScript = {
        # compare remote and local file contents and return 
        # $true if they match, or $false if they're different
        $source = "D:\temp\dsctest\source.txt";
        $target = "D:\temp\dsctest\target.txt";
        $isMatch = [System.IO.File]::ReadAllText($target) -eq [System.IO.File]::ReadAllText($source);
        write-verbose "isMatch = $isMatch";
        return $isMatch;
    }
    SetScript  = {
        # overwrite the local file
        $source = "D:\temp\dsctest\source.txt";
        $target = "D:\temp\dsctest\target.txt";
        $content = [System.IO.File]::ReadAllText($source);
        write-verbose "overwriting config";
        [System.IO.File]::WriteAllText($target, $content);
        # restart the service
        write-verbose "restarting service";
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM