Skip to content

Instantly share code, notes, and snippets.

@msutter
Last active December 12, 2017 09:07
Show Gist options
  • Save msutter/172aeabab0cd67e54cd30efc4a9f3b19 to your computer and use it in GitHub Desktop.
Save msutter/172aeabab0cd67e54cd30efc4a9f3b19 to your computer and use it in GitHub Desktop.

External facts

External facts provide a way to use arbitrary powershell scripts as facts, or set facts statically with data structured as INIFILE, JSON or YAML. If you’ve ever wanted to write a custom fact in Powershell, this is how.

Facts location

To add external facts to your Puppet modules, just place them in MODULEPATH/MODULENAME/facts.d/. at the same level as you would place manifests, files or templates directories.

They will be automatically distributed with pluginsync during the puppet run.

The external facts are located in the following common directory.

C:\ProgramData\PuppetLabs\facter\facts.d\

Structured data facts

Facter can parse structured data files stored in the external facts directory and set facts based on their contents. Structured data files must use one of the supported data types and must have the correct file extension.

The file encoding must be either ANSI or UTF8 without BOM (Byte Order Mark). Here an example how you can write a file as UTF8 without BOM encoding in powershell:

$json_example_content = '{"key1": "val1", "key2": "val2"}'
Set-Content -Encoding Ascii -Path "${externalFactsLocation}/json_example.json" -Value $content

Facter supports the following extensions and data types:


  • .yaml: YAML data, in the following format:
---
key1: val1
key2: val2
key3: val3

  • .json: JSON data, in the following format:
{
    "key1": "val1",
    "key2": "val2",
    "key3": "val3"
}

  • .txt: Key value pairs, in the following format:
key1=value1
key2=value2
key3=value3

Structured data files can set multiple facts at once.

{
  "datacenter":
  {
    "location": "bfs",
    "workload": "Web Development Pipeline",
    "contact": "Blackbird"
  },
  "provision":
  {
    "birth": "2017-01-01 14:23:34",
    "user": "alex"
  }
}

Executable facts

Executable facts on Windows work by dropping an executable file into the external fact path. The external facts interface expects Windows scripts to end with a known extension. The following extensions are currently supported:

  • .com and .exe: binary executables
  • .bat and .cmd: batch scripts
  • .ps1: PowerShell scripts

Each script must return key/value pairs on STDOUT in the format:

key1=value1
key2=value2
key3=value3

Using this INIFILE format, a single script can return multiple facts in one return.

CAUTION: Only the INIFILE format is supported as executable fact output. See bellow for a workaround.

Here is a sample PowerShell script which outputs facts using the required format:

Write-Host "key1=val1"
Write-Host 'key2=val2'
Write-Host key3=val3

Workaround the structured fact limitation on executable facts

As a workaround, if you need to generate a structured fact dynamically, you cannot use the script output, but you can write a file with the correct extension. This file will be interpreted by facter in the same run as the execution of the script writing the file.

Here an example with a powershell script writing a .json file. The script has to be located in the facts.d directory. We named it 'psversiontable.ps1'. At each facter invokation, the script will generate a new psversiontable.json file.

# create a hashtable with a 'psversiontable' key (this will be the fact name) and use $psversiontable for the value
# also create an additional key 'psversion' with the psversion as string. This will be a second fact.
$PSVersionTableFact = @{
    "psversiontable" = $PSVersionTable;
    "psversion"      = $PSVersionTable.PSVersion.ToString();
}

# convert the powershell hashtable in a json format
$content = $PSVersionTableFact | ConvertTo-Json

# Write the external fact file in the same directory ($PSScriptRoot)
Set-Content -Encoding Ascii -Path "${PSScriptRoot}/psversiontable.json" -Value $content

You should be able use this fact with:

$ facter -p psversiontable
{
  BuildVersion => {
    Build => 14409,
    Major => 10,
    MajorRevision => 0,
    Minor => 0,
    MinorRevision => 1005,
    Revision => 1005
  },
  CLRVersion => {
    Build => 30319,
    Major => 4,
    MajorRevision => 0,
    Minor => 0,
    MinorRevision => -23536,
    Revision => 42000
  },
  PSCompatibleVersions => [
    "1.0",
    "2.0",
    "3.0",
    "4.0",
    "5.0",
    "5.1.14409.1005"
  ],
  PSEdition => "Desktop",
  PSRemotingProtocolVersion => {
    Build => -1,
    Major => 2,
    MajorRevision => -1,
    Minor => 3,
    MinorRevision => -1,
    Revision => -1
  },
  PSVersion => {
    Build => 14409,
    Major => 5,
    MajorRevision => 0,
    Minor => 1,
    MinorRevision => 1005,
    Revision => 1005
  },
  SerializationVersion => {
    Build => 0,
    Major => 1,
    MajorRevision => 0,
    Minor => 1,
    MinorRevision => 1,
    Revision => 1
  },
  WSManStackVersion => {
    Build => -1,
    Major => 3,
    MajorRevision => -1,
    Minor => 0,
    MinorRevision => -1,
    Revision => -1
  }
}

As it's a structured fact, you can use the dot notation:

$ facter -p psversiontable.PSVersion.Major
5

Our additional 'psversion' fact is also available:

$ facter -p psversion
5.1.14409.1005
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment