Writing Idempotent PowerShell scripts

14 Oct 2019

Introduction

We hear a lot about configuration management tools and how we should all be using them, but what do we do when we cannot use them or when we need to extend them to do extra things? Well that is where we need to ensure our scripts are idempotent.

What does idempotent mean?

The dictionary defines idempotent to mean “denoting an element of a set which is unchanged in value when multiplied or otherwise operated on by itself”. When we talk about scripting we mean that we can run the script multiple times, each time it exits with the same results without other side effects.

So how do we do this?

In this we will use a file called “testfile.txt” and it will have the words “This is a test file!” inside of it. This could be done with PowerShell DSC however it is a nice example to use.

So firstly we need to have the code to create the file assuming it doesn’t exist.

$file = "testfile.txt"
New-Item -Path $file -ItemType File
$fileContent = "This is a test file!"
Set-Content -Path $file -Value $fileContent

Obviously everytime we run this we will get a different output due to the fact that if the file exists this will then error. This in it’s current state is the exact opposite of idempotent as it will give us a file object when the file doesn’t exist and will give us an error when the file does exist.

The inital point to combat the file object is redirect the output from New-Item to Out-Null.

$file = "testfile.txt"
New-Item -Path $file -ItemType File | Out-Null
$fileContent = "This is a test file!"
Set-Content -Path $file -Value $fileContent

The next thing to do is to have a common output which will be $file exists which in our case is “testfile.txt exists”, this way we can control the output

$file = "testfile.txt"
New-Item -Path $file -ItemType File | Out-Null
$fileContent = "This is a test file!"
Set-Content -Path $file -Value $fileContent
Write-Output "$file exists"

Logic

Logic is how we convert our scripts from non-idempotent to idempotent where we can run the script as many times as we please and always get the same results. This is where we can start to handle the errors that would come from trying to create a file that already exists.

The logic we need for this example is as follows:

  1. Does the file exist?
  2. Does the content of the file match the expected content?

Logic 1 - Does the file exist

So the inital test is does the file exist as we expect, remembering that whatever happens we want the output to be “testfile.txt exists”. If the file doesn’t exist then we need to create the file and also insert the expected content into file that we create.

$file = "testfile.txt"
$fileContent = "This is a test file!"
if (!(Test-Path -Path $file)) {
    New-Item -Path $file -ItemType File | Out-Null
    Set-Content -Path $file -Value $fileContent
}
Write-Output "$file exists"

This should only ever output “testfile.txt exists”.

Logic 2 - Does the content of the file match the expected content

If the file does exist then we need to verify the content of that file. If the content of the file is different from the expected value then we need to set the value to the correct content.

$file = "testfile.txt"
$fileContent = "This is a test file!"
if (!(Test-Path -Path $file)) {
    New-Item -Path $file -ItemType File | Out-Null
    Set-Content -Path $file -Value $fileContent
}
else {
    $content = Get-Content -Path $file
    if ($content -ne $fileContent) {
        Set-Content -Path $file -Value $fileContent
    }
}
Write-Output "$file exists"

Once again no matter how many times this is run the only thing that you should recieve back is “testfile.txt exists”.

Roundup

This leaves us with a script that could be used to verify a file, of course both $file and $filecontent could be parameters.