Managing Windows nodes

You can use Puppet Enterprise (PE) to manage your Windows configurations, including controlling services, creating local group and user accounts, and performing basic management tasks with modules from the Forge.

Make sure you understand how to run Commands with elevated privileges.

For general information about forming curl commands, authentication in commands, and Windows modifications, go to Using example commands.

Basic tasks and concepts in Windows

This section is meant to help familiarize you with several common tasks used in Puppet Enterprise (PE) with Windows agents, and explain the concepts and reasons behind performing them.

You'll create a simple manifest file and use it to perform some common actions.

Practice tasks

You'll encounter these tasks as part of other tasks throughout the Puppet Enterprise (PE) documentation. We've provided additional explanation here for your reference.

Write a simple manifest

Puppet manifest files are lists of resources that have a unique title and a set of named attributes that describe the desired state.

Before you begin
You need a text editor, such as Visual Studio Code (VSCode), to create manifest files. Puppet has a VSCode extension that supports syntax highlighting of the Puppet language. Editors like Notepad++ or Notepad don't highlight Puppet syntax, but you can use them to create manifests.
Manifest files are written in Puppet code, a domain specific language (DSL), and define the desired state of system resources, such as file, users, and packages. Puppet compiles these text-based manifests into catalogs, and uses those catalogs to apply configuration changes.
  1. Create a file named file.pp and save it in c:\myfiles\
  2. With your text editor of choice, add the following code to the file:
    file { 'c:\\Temp\\foo.txt':
      ensure   => present,
      content  => 'This is some text in my file'
    }
    Note the following details in this file resource example:
    • Puppet uses a basic syntax of type { title: }, where type is the resource type. In this case, the resource type is file.
    • The vvalue before the : is the resource title. In this example, the title is C:\\Temp\\foo.txt. The file resource uses the title to determine where to create the file on disk. Resource titles must always be unique within a given manifest.
    • The ensure parameter is set to present to make sure the file exists on disk and create the file if it doesn't already exist. For file type resources, you can also use the value absent, which removes the file from disk if it exists.
    • The content parameter is set to This is some text in my file, which writes that value to the file.

Launch the Puppet command prompt

A lot of common interactions with Puppet are done via the command line.

To open the command line interface, enter Command Prompt Puppet in your Start Menu, and click Start Command Prompt with Puppet.
Results
Note these details about the Puppet command prompt:
  • Several important batch files live in the current working directory, which is at C:\Program Files\Puppet Labs\Puppet\bin. The most important of these batch files is puppet.bat. Puppet is a Ruby based application, and puppet.bat is a wrapper around executing Puppet code through ruby.exe.
  • Running the command prompt with Puppet rather than just the default Windows command prompt ensures that all of the Puppet tooling is in PATH, even if you change to a different directory.

Validate your manifest with puppet parser validate

You can validate that a manifest's syntax is correct by using the puppet parser validate command.

  1. Open the Puppet command prompt and check your syntax by running: puppet parser validate c:\myfiles\file.pp
    If the manifest has no syntax errors, the tool outputs nothing.
  2. To preview the error output, edit your sample manifest file to remove the : after the resource title, and run puppet parser validate c:\myfiles\file.pp again to return the error response:
    Error: Could not parse for environment production: Syntax error at 'ensure' at c:/myfiles/file.pp:2:3

Simulate a Puppet run with --noop

Puppet has a switch that you can use to test if manifests make your intended changes. This is referred to as non-enforcement mode or no-op mode.

To simulate changes, run puppet apply c:\myfiles\file.pp --noop in the command prompt and observe the result:
C:\Program Files\Puppet Labs\Puppet\bin>puppet apply c:\myfiles\file.pp --noop
Notice: Compiled catalog for win-User.localdomain in environment production in 0.45 seconds
Notice: /Stage[main]/MainFile[C:\Temp\foo.txt]/ensure: current value absent, should be present (noop)
Notice: Class[Main]: Would have triggered 'refresh' from 1 events
Notice: Stage[main]: Would have triggered 'refresh' from 1 events
Notice: Applied catalog in 0.03 seconds
Results
Puppet shows you the changes it would make, but does not actually make the changes. In the above example, it would create a new file at C:\Temp\foo.txt, but it hasn't, because you used --noop.

Enforce the desired state with puppet apply

If you are satisfied with the outcome of a no-op run, you can start enforcing the changes with the puppet apply command.

Run puppet apply with the desired manifest file, such as: puppet apply c:\myfiles\file.pp
To see more details about what this command did, you can specify additional options, such as --trace, --debug, or --verbose, which can help you diagnose problematic code. If puppet apply fails, Puppet outputs a full stack trace.
Results
Puppet enforces the resource state you've described in file.pp, in this case guaranteeing that a file (c:\Temp\foo.txt) is present and has the contents This is some text in my file.

Understanding idempotency

A key feature of Puppet is its idempotency: The ability to repeatedly apply a manifest to guarantee a desired resource state on a system, with the same results every time.

If a given resource is already in the desired state, Puppet performs no actions. If a given resource is not in the desired state, Puppet takes whatever action is necessary to put the resource into the desired state. Idempotency enables Puppet to simulate resource changes without performing them, and lets you set up configuration management one time, fixing configuration drift without recreating resources from scratch each time Puppet runs.

To demonstrate how Puppet can be applied repeatedly to get the same results:
  1. Change the manifest at c:\myfiles\file.pp to the following:
    file { 'C:\\Temp\\foo.txt':
      ensure   => present,
      content  => 'I have changed my file content.'
    }
  2. Apply the manifest by running: puppet apply c:\myfiles\file.pp
  3. Open c:\Temp\foo.txt and notice that Puppet changed the file's contents.

Applying the manifest again (with puppet apply c:\myfiles\file.pp) results in no changes to the system, because the file already exists in the desired state, thereby demonstrating that Puppet behaves idempotently.

Many of the samples in the Puppet documentation assume that you have this basic understanding of creating and editing manifest files, and applying them with puppet apply.

Additional command line tools

Once you understand how to write manifests, validate them, and use puppet apply to enforce your changes, you're ready to use commands such as puppet agent, puppet resource, and puppet module install.

puppet agent

Like puppet apply, the puppet agent command line tool applies configuration changes to a system. However, puppet agent retrieves compiled catalogs from a Puppet Server, and applies them to the local system. Puppet is installed as a Windows service, and by default tries to contact the primary server every 30 minutes by running puppet agent to retrieve new catalogs and apply them locally.

puppet resource

You can run puppet resource to query the state of a particular type of resource on the system. For example, to list all of the users on a system, run the command puppet resource user.
A terminal window showing user information, such as user groups and user ID, returned by the puppet resource user command.

The computer used for this example has three local user accounts: Administrator, Guest, and vagrant. Note that the output is the same format as a manifest, and you can copy and paste it directly into a manifest.

puppet module install

Puppet includes many core resource types, plus you can extend Puppet by installing modules. Modules contain additional resource definitions and the code necessary to modify a system to create, read, modify, or delete those resources. The Puppet Forge contains modules developed by Puppet and community members available for anyone to use.

Puppet synchronizes modules from a primary server to agent nodes during puppet agent runs. Alternatively, you can use the standalone Puppet module tool, included when you install Puppet, to manage, view, and test modules.

Run puppet module list to show the list of modules installed on the system.

To install modules, the Puppet module tool uses the syntax puppet module install NAMESPACE/MODULENAME. The NAMESPACE is registered to a module, and MODULE refers to the specific module name. A very common module to install on Windows is registry, under the puppetlabs namespace. So, to install the registry module, run puppet module install puppetlabs/registry.

Manage Windows services

You can use Puppet to manage Windows services, specifically, to start, stop, enable, disable, list, query, and configure services. This way, you can ensure that certain services are always running or are disabled as necessary.

You write Puppet code to manage services in the manifest. When you apply the manifest, the changes you make to the service are applied.

Note: In addition to using manifests to apply configuration changes, you can query system state using the puppet resource command, which emits code as well as applying changes.

Ensure a Windows service is running

There are often services that you always want running in your infrastructure.

To have Puppet ensure that a service is running, use the following code:
service { '<service name>':
       ensure => 'running'
    }

Example

For example, the following manifest code ensures the Windows Time service is running:
service { 'w32time':
       ensure => 'running'
    }

Stop a Windows service

Some services can impair performance, or might need to be stopped for regular maintenance.

To disable a service, use the code:
service { '<service name>':
       ensure => 'stopped',
       enable => 'false'
    }

Example

For example, this disables the disk defragmentation service, which can negatively impact service performance.
 service { 'defragsvc':
       ensure => 'stopped',
       enable => 'false'
    }

Schedule a recurring operation with Windows Task Scheduler

Regularly scheduled operations, or tasks, are often necessary on Windows to perform routine system maintenance.

Note: If you need to run an ad-hoc task in the PE console or on the command line, see Running tasks in PE.

If you need to sync files from another system on the network, perform backups to another disk, or execute log or index maintenance on SQL Server, you can use Puppet to schedule and perform regular tasks. The following shows how to regularly delete files.

To delete all files recursively from C:\Windows\Temp at 8 AM each day, create a resource called scheduled_task with these attributes:
scheduled_task { 'Purge global temp files':
   ensure    => present,
   enabled   => true,
   command   => 'c:\\windows\\system32\\cmd.exe',
   arguments => '/c "del c:\\windows\\temp\\*.* /F /S /Q"',
   trigger   => {
      schedule   => daily,
      start_time => '08:00',
  }
}
Results
After you set up Puppet to manage this task, the Task Scheduler includes the task you specified:
The task scheduler showing that the task has been added to the task list.

Example

In addition to creating a trivial daily task at a specified time, the scheduled task resource supports a number of other more advanced scheduling capabilities, including more fine-tuned scheduling. For example, to change the above task to instead perform a disk clean-up every 2 hours, modify the trigger definition:

scheduled_task { 'Purge global temp files every 2 hours':
   ensure  => present,
   enabled => true,
   command   => 'c:\\windows\\system32\\cmd.exe',
   arguments => '/c "del c:\\windows\\temp\\*.* /F /S /Q"',
   trigger => [{
      day_of_week => ['mon', 'tues', 'wed', 'thurs', 'fri'],
      every => '1',
      minutes_interval => '120',
      minutes_duration => '1440',
      schedule => 'weekly',
      start_time => '07:30'
   }],
  user => 'system',
}
You can see the corresponding definition reflected in the Task Scheduler GUI:
The Task Scheduler's "Edit Trigger" window, showing task schedule options such as how often and on what days to repeat the task.

Manage Windows users and groups

Puppet can be used to create local group and user accounts. Local user accounts are often desirable for isolating applications requiring unique permissions.

Manage administrator accounts

It is often necessary to standardize the local Windows Administrator password across an entire Windows deployment.

To manage administrator accounts with Puppet, create a user resource with 'Administrator' as the resource title like so:
user { 'Administrator':
   ensure => present,
   password => '<PASSWORD>'
}
Note: Securing the password used in the manifest is beyond the scope of this introductory example, but it’s common to use Hiera, a key/value lookup tool for configuration, with eyaml to solve this problem. Not only does this solution provide secure storage for the password value, but it also provides parameterization to support reuse, opening the door to easy password rotation policies across an entire network of Windows machines.

Configure an app to use a different account

You might not always want to use the default user for an application, you can use Puppet to create users for other applications, like ASP.NET.

To configure ASP.NET apps to use accounts other than the default Network Service, create a user and exec resource:
user { 'aspnet_banking_app':
   ensure          => present,
   managehome      => true,
   comment         => 'ASP.NET Service account for Banking application',
   password        => 'banking_app_password',
   groups          => ['IIS_IUSRS', 'Users'],
   auth_membership => 'minimum',
   notify          => Exec['regiis_aspnet_banking_app']
}

exec { 'regiis_aspnet_banking_app':
   path        => 'c:\\windows\\Microsoft.NET\\Framework\\v4.0.30319',
   command     => 'aspnet_regiis.exe -ga aspnet_banking_app',
   refreshonly => true
}
Results

In this example, the user is created in the appropriate groups, and the ASP.NET IIS registration command is run after the user is created to ensure file permissions are correct.


The group properties window showing that the user in the previous example has been created.

In the user resource, there are a few important details to note:

  • managehome is set to create the user's home directory on disk.

  • auth_membership is set to minimum, meaning that Puppet makes sure the aspnet_banking_app user is a part of the IIS_IUSRS and Users group, but doesn't remove the user from any other groups it might be a part of.

  • notify is set on the user, and refreshonly is set on the exec. This tells Puppet to run aspnet_regiis.exe only when the aspnet_banking_app is created or changed.

Manage local groups

Local user accounts are often desirable for isolating applications requiring unique permissions. It can also be useful to manipulate existing local groups.

To add domain users or groups not present in the Domain Administrators group to the local Administrators group, use this code:
group { 'Administrators':
   ensure  => 'present',
   members => ['DOMAIN\\User'],
   auth_membership => false
}
Results

In this case, auth_membership is set to false to ensure that DOMAIN\User is present in the Administrators group, but that other accounts that might be present in Administrators are not removed.

Note that the groups attribute of user and the members attribute of group might both accept SID values, like the well-known SID for Administrators, S-1-5-32-544.

Executing PowerShell code

Some Windows maintenance tasks require the use of Windows Management Instrumentation (WMI), and PowerShell is the most useful way to access WMI methods. Puppet has a special module that can be used to execute arbitrary PowerShell code.

A common Windows maintenance tasks is to disable Windows drive indexing, because it can negatively impact disk performance on servers.

To disable drive indexing:
$drive = 'C:'

exec { 'disable-c-indexing':
   provider  => powershell,
   command   => "\$wmi_volume = Get-WmiObject -Class Win32_Volume -Filter 'DriveLetter=\"${drive}\"'; if (\$wmi_volume.IndexingEnabled -ne \$True) { return }; \$wmi_volume | Set-WmiInstance -Arguments @{IndexingEnabled = \$False}",
   unless    => "if ((Get-WmiObject -Class Win32_Volume -Filter 'DriveLetter=\"${drive}\"').IndexingEnabled) { exit 1 }",
}
Results
You can see the results in your object editor window:
Object editor window showing that IndexingEnabled is set to FALSE.

Using the Windows built-in WBEMTest tool, running this manifest sets IndexingEnabled to FALSE, which is the desired behavior.

This exec sets a few important attributes:
  • The provider is configured to use PowerShell (which relies on the module).

  • The command contains inline PowerShell, and as such, must be escaped with PowerShell variables preceded with $ must be escaped as \$.

  • The unless attribute is set to ensure that Puppet behaves idempotently, a key aspect of using Puppet to manage resources. If the resource is already in the desired state, Puppet does not modify the resource state.

Using templates to better manage Puppet code

While inline PowerShell is usable as an exec resource in your manifest, such code can be difficult to read and maintain, especially when it comes to handling escaping rules.

For executing multi-line scripts, use Puppet templates instead. The following example shows how you can use a template to organize the code for disabling Windows drive indexing.

$drive = 'C:'

exec { 'disable-c-indexing':
  command   => template('Disable-Indexing.ps1.erb'),
  provider  => powershell,
  unless    => "if ((Get-WmiObject -Class Win32_Volume -Filter 'DriveLetter=\"${drive}\"').IndexingEnabled) { exit 1 }",
}

The PowerShell code for Disable-Indexing.ps1.erb becomes:

function Disable-Indexing($Drive)
{
  $drive = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter='$Letter'"
  if ($drive.IndexingEnabled -ne $True) { return }
  $drive | Set-WmiInstance -Arguments @{IndexingEnabled=$False} | Out-Null
}

Disable-Indexing -Drive '<%= @driveLetter %>'

Using Windows modules

You can use modules from the Forge to perform basic management tasks on Windows nodes, such as managing access control lists and registry keys, and installing and creating your own packages.

Manage permissions with the acl module

The puppetlabs-acl module helps you manage access control lists (ACLs), which provide a way to interact with permissions for the Windows file system. This module enables you to set basic permissions up to very advanced permissions using SIDs (Security Identifiers) with an access mask, inheritance, and propagation strategies. First, start with querying some existing permissions.

View file permissions with ACL

ACL is a custom type and provider, so you can use puppet resource to look at existing file and folder permissions.

For some types, you can use the command puppet resource <TYPE NAME> to get all instances of that type. However, there could be thousands of ACLs on a Windows system, so it's best to specify the folder you want to review the types in. Here, check c:\Users to see what permissions it contains.

In the command prompt, enter puppet resource acl c:\Users
acl { 'c:\Users':
  inherit_parent_permissions => 'false',
  permissions                => [
   {identity => 'SYSTEM', rights=> ['full']},
   {identity => 'Administrators', rights => ['full']},
   {identity => 'Users', rights => ['read', 'execute'], affects => 'self_only'},
   {identity => 'Users', rights => ['read', 'execute'], affects => 'children_only'},
   {identity => 'Everyone', rights => ['read', 'execute'], affects => 'self_only'},
   {identity => 'Everyone', rights => ['read', 'execute'], affects => 'children_only'}
  ],
}
Results

As you can see, this particular folder does not inherit permissions from its parent folder; instead, it sets its own permissions and determines how child files and folders inherit the permissions set here.

  • {'identity' => 'SYSTEM', 'rights'=> ['full']} states that the “SYSTEM” user has full rights to this folder, and by default all children and grandchildren files and folders (as these are the same defaults when creating permissions in Windows).

  • {'identity' => 'Users', 'rights' => ['read', 'execute'], 'affects' => 'self_only'} gives read and execute permissions to Users but only on the current directory.

  • {'identity' => 'Everyone', 'rights' => ['read', 'execute'], 'affects' => 'children_only'} gives read and execute permissions to everyone, but only on subfolders and files.

Note: You might see what appears to be the same permission for a user/group twice (both "Users" and "Everyone" above), where one affects only the folder itself and the other is about children only. They are in fact different permissions.

Create a Puppet managed permission

  1. Run this code to create your first Puppet managed permission. Then, save it as perms.pp
    file{'c:/tempperms':
     ensure => directory,
    }
    
    # By default, the acl creates an implicit relationship to any
    # file resources it finds that match the location.
    acl {'c:/tempperms':
     permissions => [
       {identity => 'Administrators', rights => ['full']},
       {identity => 'Users', rights => ['read','execute']}
     ],
    }
  2. To validate your manifest, in the command prompt, run puppet parser validate c:\<FILE PATH>\perms.pp. If the parser returns nothing, it means validation passed.
  3. To apply the manifest, type puppet apply c:\<FILE PATH>\perms.pp

    Your output should look similar to:

    Notice: Compiled catalog for win2012r2x64 in environment production in 0.12 seconds
    Notice: /Stage[main]/Main/File[c:/tempperms]/ensure: created
    Notice: /Stage[main]/Main/Acl[c:/tempperms]/permissions: permissions changed [
    ] to [
     { identity => 'BUILTIN\Administrators', rights => ["full"] },
     { identity => 'BUILTIN\Users', rights => ["read", "execute"] }
    ]
    Notice: Applied catalog in 0.05 seconds
  4. Review the permissions in your Windows UI. In Windows Explorer, right-click tempperms and click Properties. Then, click the Security tab. It should appear similar to the image below.

    The tempperms window with the security tab highlighted.
  5. Optional: It might appear that you have more permissions than you were hoping for here. This is because by default Windows inherits parent permissions. In this case, you might not want to do that. Adjust the acl resource to not inherit parent permissions by changing the perms.pp file to look like the below by adding inherit_parent_permissions => false.
    acl {'c:/tempperms':
      inherit_parent_permissions => false,
      permissions                => [
        {identity => 'Administrators', rights => ['full']},
        {identity => 'Users', rights => ['read','execute']}
      ],
    }
  6. Save the file, and return the command prompt to run puppet parser validate c:\<FILE PATH>\perms.pp again.
  7. When it validates, run puppet apply c:\<FILE PATH>\perms.pp

    You should get output similar to the following:

    C:\>puppet apply c:\puppet_code\perms.pp
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.08 seconds
    Notice: /Stage[main]/Main/Acl[c:/tempperms]/inherit_parent_permissions: inherit_
    parent_permissions changed 'true' to 'false'
    Notice: Applied catalog in 0.02 seconds
  8. To check the permissions again, enter icacls c:\tempperms in the command prompt. The command, icacls, is specifically for displaying and modifying ACLs. The output should be similar to the following:
    C:\>icacls c:\tempperms
    c:\tempperms BUILTIN\Administrators:(OI)(CI)(F)
                 BUILTIN\Users:(OI)(CI)(RX)
                 NT AUTHORITY\SYSTEM:(OI)(CI)(F)
                 BUILTIN\Users:(CI)(AD)
                 CREATOR OWNER:(OI)(CI)(IO)(F)
    Successfully processed 1 files; Failed processing 0 files
    The output shows each permission, followed by a list of specific rights in parentheses. This output shows there are more permissions than you specified in perms.pp. Puppet manages permissions next to unmanaged or existing permissions. In the case of removing inheritance, by default Windows copies those existing inherited permissions (or Access Control Entries, ACEs) over to the existing ACL so you have some more permissions that you might not want.
  9. Remove the extra permissions, so that only the permissions you’ve specified are on the folder. To do this, in your perms.pp set purge => true as follows:
    acl {'c:/tempperms':
      inherit_parent_permissions => false,
      purge                      => true,
      permissions                => [
        {identity => 'Administrators', rights => ['full']},
        {identity => 'Users', rights => ['read','execute']}
      ],
    }
  10. Run the parser command as you have before. If it still returns no errors, then you can apply the change.
  11. To apply the change, run puppet apply c:\<FILE PATH>\perms.pp. The output should be similar to below:
    C:\>puppet apply c:\puppet_code\perms.pp
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.08 seco
    nds
    Notice: /Stage[main]/Main/Acl[c:/tempperms]/permissions: permissions changed [
    { identity => 'BUILTIN\Administrators', rights => ["full"] },
    { identity => 'BUILTIN\Users', rights => ["read", "execute"] },
    { identity => 'NT AUTHORITY\SYSTEM', rights => ["full"] },
    { identity => 'BUILTIN\Users', rights => ["mask_specific"], mask => '4', child_types => 'containers' },
    { identity => 'CREATOR OWNER', rights => ["full"], affects => 'children_only' }
    ] to [
    { identity => 'BUILTIN\Administrators', rights => ["full"] },
    { identity => 'BUILTIN\Users', rights => ["read", "execute"] }
    ]
    Notice: Applied catalog in 0.05 seconds
    Puppet outputs a notice as it is removing each of the permissions.
  12. Take a look at the output of icacls again with icacls c:\tempperms
    c:\>icacls c:\tempperms
    c:\tempperms BUILTIN\Administrators:(OI)(CI)(F)
                BUILTIN\Users:(OI)(CI)(RX)
    Successfully processed 1 files; Failed processing 0 files
Results

Now the permissions have been set up for this directory. You can get into more advanced permission scenarios if you read the usage scenarios on this module’s Forge page.

Create managed registry keys with registry module

You might eventually need to use the registry to access and set highly available settings, among other things. The puppetlabs-registry module, which is also a Puppet Supported Module enables you to set both registry keys and values.

View registry keys and values with puppet resource

puppetlabs-registry is a custom type and provider, so you can use puppet resource to examine existing registry settings.

This command is somewhat limited, like the acl module, in that it is restricted to only what is specified.

Keys are like file paths (directories), and values are like files that can have data and be of different types.

  1. To examine a registry key, open a command prompt and run:
    puppet resource registry_key 'HKLM\Software\Microsoft\Windows'
    registry_key { 'HKLM\Software\Microsoft\Windows\':
      ensure => 'present',
    }
  2. To examine a registry value, run:
    puppet resource registry_value 'HKLM\SYSTEM\CurrentControlSet\Services\BITS\DisplayName'
    registry_value { 'HKLM\SYSTEM\CurrentControlSet\Services\BITS\DisplayName':
      ensure => 'present',
      data   => ['Background Intelligent Transfer Service'],
      type   => 'string',
    }

Create managed keys

Learn how to make managed registry keys, and see Puppet correct configuration drift when you try and alter them in Registry Editor.

  1. Create your first Puppet managed registry keys and values:
    registry_key { 'HKLM\Software\zTemporaryPuppet':
         ensure => present,
    }
    
    # By default the registry creates an implicit relationship to any file
    # resources it finds that match the location.
    registry_value {'HKLM\Software\zTemporaryPuppet\StringValue':
      ensure => 'present',
      data   => 'This is a custom value.',
      type   => 'string',
    }
    
    #forcing a 32-bit registry view; watch where this is created:
    registry_key { '32:HKLM\Software\zTemporaryPuppet':
      ensure => present,
    }
    
    registry_value {'32:HKLM\Software\zTemporaryPuppet\StringValue':
      ensure => 'present',
      data   => 'This is a custom 32-bit value.',
      type   => 'expand',
    }
  2. Save the file as registry.pp.
  3. Validate the manifest. In the command prompt, run puppet parser validate c:\<FILE PATH>\registry.pp

    If the parser returns nothing, it means validation passed.

  4. Now, apply the manifest by running puppet apply c:\<FILE PATH>\registry.pp in the command prompt. Your output should look similar to below.
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.11 seco
    nds
    Notice: /Stage[main]/Main/Registry_key[HKLM\Software\zTemporaryPuppet]/ensure: c
    reated
    Notice: /Stage[main]/Main/Registry_value[HKLM\Software\zTemporaryPuppet\StringVa
    lue]/ensure: created
    Notice: /Stage[main]/Main/Registry_key[32:HKLM\Software\zTemporaryPuppet]/ensure
    : created
    Notice: /Stage[main]/Main/Registry_value[32:HKLM\Software\zTemporaryPuppet\Strin
    gValue]/ensure: created
    Notice: Applied catalog in 0.03 seconds
  5. Next, inspect the registry and see what you have. Press Start + R, then type regedit and press Enter. Once the Registry Editor opens, find your keys under HKEY_LOCAL_MACHINE.

    The Registry Editor window showing keys in the zTemporaryPuppet subfolder.
    Note that the 32-bit keys were created under the 32-bit section of Wow6432Node for Software.
  6. Apply the manifest again by running puppet apply c:\<FILE PATH>\registry.pp
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.11 seconds
    Notice: Applied catalog in 0.02 seconds

    Nothing changed, so there is no work for Puppet to do.

  7. In Registry Editor, change the data. Select HKLM\Software\zTemporaryPuppet and in the right box, double-click StringValue. Edit the value data, and click OK.

    A screenshot of the Registry Editor with pointers showing where to select the key, double click on the StringValue, and edit the value data for that StringValue.
    This time, changes have been made, so running puppet apply c:\path\to\registry.pp results in a different output.
    Notice: Compiled catalog for win2012r2x64 in environment production
    in 0.11 seconds
    Notice: /Stage[main]/Main/Registry_value[HKLM\Software\zTemporaryPuppet\StringValue]/data:
    data changed 'This is a custom value. Edited' to 'This is a custom value.'
    Notice: Applied catalog in 0.03 seconds

    Puppet automatically corrects the configuration drift.

  8. Next, clean up and remove the keys and values. Make your registry.pp file look like the below:
    registry_key { 'HKLM\Software\zTemporaryPuppet':
      ensure => absent,
    }
    
    #forcing a 32 bit registry view, watch where this is created
    registry_key { '32:HKLM\Software\zTemporaryPuppet':
      ensure => absent,
    }
  9. Validate it with puppet parser validate c:\path\to\registry.pp and apply it again with puppet apply c:\path\to\registry.pp
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.06 seconds
    Notice: /Stage[main]/Main/Registry_key[HKLM\Software\zTemporaryPuppet]/ensure: removed
    Notice: /Stage[main]/Main/Registry_key[32:HKLM\Software\zTemporaryPuppet]/ensure
    : removed
    Notice: Applied catalog in 0.02 seconds
    Refresh the view in your Registry Editor. The values are gone.

    Screenshot of the Registry Editor showing that the adjusted value is gone.
Example
Here’s a real world example that disables error reporting:
class puppetconf::disable_error_reporting {
  registry_value { 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\ForceQueue':
    type => dword,
    data => '1',
  }

  registry_value { 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\DontShowUI':
    type => dword,
    data => '1',
  }

  registry_value { 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\DontSendAdditionalData':
    type => dword,
    data => '1',
  }

  registry_key { 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Consent':
    ensure       => present,
  }

  registry_value { 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Consent\DefaultConsent':
   type => dword,
   data => '2',
  }
}

Create, install, and repackage packages with the chocolatey module

Chocolatey is a package manager for Windows that is similar in design and execution to package managers on non-Windows systems. The chocolatey module is a Puppet Approved Module, so it's not eligible for Puppet Enterprise support services. The module has the capability to intall and configure Chocolatey itself, and then manage software on Windows with Chocolatey packages.

View existing packages

Chocolatey has a custom provider for the package resource type, so you can use puppet resource to view existing packages.

In the command prompt, run puppet resource package --param provider | more

The additional provider parameter in this command outputs all types of installed packages that are detected by multiple providers.

Install Chocolatey

These steps are to install Chocolatey (choco.exe) itself. You use the chocolatey module to ensure Chocolatey is installed.

  1. Create a new manifest in the chocolatey module called chocolatey.pp with the following contents:
    include chocolatey
  2. Validate the manifest by running: puppet parser validate c:\<FILE PATH>\chocolatey.pp in the command prompt. If the parser returns nothing, it means validation passed.
  3. Apply the manifest by running: puppet apply c:\<FILE PATH>\chocolatey.pp
    Make sure the output is similar to the following:
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.58 seconds
    Notice: /Stage[main]/Chocolatey::Install/Windows_env[chocolatey_PATH_env]/ensure
    : created
    Notice: /Stage[main]/Chocolatey::Install/Windows_env[chocolatey_ChocolateyInstal
    l_env]/ensure: created
    Notice: /Stage[main]/Chocolatey::Install/Exec[install_chocolatey_official]/retur
    ns: executed successfully
    Notice: /Stage[main]/Chocolatey::Install/Exec[install_chocolatey_official]: Trig
    gered 'refresh' from 2 events
    Notice: Finished catalog run in 13.22 seconds

    In a production scenario, you’ll likely have a Chocolatey.nupkg file located somewhere internally. In these cases, you can use the internal nupkg to install Chocolatey, such as:

    class {'chocolatey':
      chocolatey_download_url => 'https://internalurl/to/chocolatey.nupkg',
      use_7zip                => false,
      log_output              => true,
    }

Install a package with chocolatey

Normally, when installing packages you copy them locally first, make any required changes to bring everything they download to an internal location, repackage the package with the edits, and build your own packages to host on your internal package repository (feed). For this exercise, however, you directly install a portable Notepad++ from Chocolatey's community feed. The Notepad++ CommandLine package is portable and shouldn't greatly affect an existing system.

  1. Update the manifest chocolatey.pp with the following contents:
    package {'notepadplusplus.commandline':
      ensure   => installed,
      provider => chocolatey,
    }
  2. Validate the manifest by running puppet parser validate c:\<FILE PATH>\chocolatey.pp in the command prompt. If the parser returns nothing, it means validation passed.
  3. Now, apply the manifest with puppet apply c:\<FILE PATH>\chocolatey.pp. Your output should look similar to below.
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.75 seconds
    Notice: /Stage[main]/Main/Package[notepadplusplus.commandline]/ensure: created
    Notice: Applied catalog in 15.51 seconds
Results

If you want to use this package for a production scenario, you need an internal custom feed. This is simple to set up with the chocolatey_server module. You could also use Sonatype Nexus, Artifactory, or a CIFS share if you want to host packages with a non-Windows option, or you can use anything on Windows that exposes a NuGet OData feed (Nuget is the packaging infrastructure that Chocolatey uses). See the How To Host Feed page of the chocolatey wiki for more in-depth information. You could also store packages on your primary server and use a file resource to verify they are in a specific local directory prior to ensuring the packages.

Example

The following example ensures that Chocolatey, the Chocolatey Simple Server (an internal Chocolatey package repository), and some packages are installed. It requires the additional chocolatey/chocolatey_server module.

In c:\<FILE PATH>\packages you must have packages for Chocolatey, Chocolatey.Server, RoundhousE, Launchy, and Git, as well as any of their dependencies for this to work.

case $operatingsystem {
  'windows':    {
    Package {
      provider => chocolatey,
      source   => 'C:\packages',
    }
  }
}

# include chocolatey
class {'chocolatey':
  chocolatey_download_url => 'file:///C:/packages/chocolatey.0.9.9.11.nupkg',
  use_7zip                => false,
  log_output              => true,
}

# This contains the bits to install the custom server.
# include chocolatey_server
class {'chocolatey_server':
  server_package_source => 'C:/packages',
}

package {'roundhouse':
  ensure   => '0.8.5.0',
}


package {'launchy':
  ensure          => installed,
  install_options => ['-override', '-installArgs','"', '/VERYSILENT','/NORESTART','"'],
}

package {'git':
  ensure => latest,
}

Copy an existing package and make it internal (repackaging packages)

To make the existing package local, use these steps.

Chocolatey's community feed has quite a few packages, but they are geared towards community and use the internet for downloading from official distribution sites. However, they are attractive as they have everything necessary to install a piece of software on your machine. Through the repackaging process, by which you take a community package and bring all of the bits internal or embed them into the package, you can completely internalize a package to host on an internal Chocolatey/NuGet repository. This gives you complete control over a package and removes the aforementioned production trust and control issues.

  1. Download the Notepad++ package from Chocolatey's community feed by going to the package page and clicking the download link.
  2. Rename the downloaded file to end with .zip and unpack the file as a regular archive.
  3. Delete the _rels and package folders and the [Content_Types].xml file. These are created during choco pack and should not be included, because they're regenerated (and their existence leads to issues).
    notepadplusplus.commandline.6.8.7.nupkg     
    ├───_rels   # DELETE
    ├───package # DELETE
    │   └───services
    ├───tools
    ├── [Content_Types].xml	# DELETE
    └── notepadplusplus.commandline.nuspec
  4. Open tools\chocolateyInstall.ps1.
    Install-ChocolateyZipPackage 'notepadplusplus.commandline' 'https://notepad-plus-plus.org/repository/6.x/6.8.7/npp.6.8.7.bin.zip' "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
  5. Download the zip file and place it in the tools folder of the package.
  6. Next, edit chocolateyInstall.ps1 to point to this embedded file instead of reaching out to the internet (if the size of the file is over 50MB, you might want to put it on a file share somewhere internally for better performance).
    $toolsDir   = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
    Install-ChocolateyZipPackage 'notepadplusplus.commandline' "$toolsDir\npp.6.8.7.bin.zip" "$toolsDir"

    The double quotes allow for string interpolation (meaning variables get interpreted instead of taken literally).

  7. Next, open the *.nuspec file to view its contents and make any necessary changes.
    <?xml version="1.0"?>
    <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
      <metadata>
        <id>notepadplusplus.commandline</id>
        <version>6.8.7</version>
        <title>Notepad++ (Portable, CommandLine)</title>
        <authors>Don Ho</authors>
        <owners>Rob Reynolds</owners>
        <projectUrl>https://notepad-plus-plus.org/</projectUrl>
        <iconUrl>https://cdn.rawgit.com/ferventcoder/chocolatey-packages/02c21bebe5abb495a56747cbb9b4b5415c933fc0/icons/notepadplusplus.png</iconUrl>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <description>Notepad++ is a ... </description>
        <summary>Notepad++ is a free (as in "free speech" and also as in "free beer") source code editor and Notepad replacement that supports several languages. </summary>
        <tags>notepad notepadplusplus notepad-plus-plus</tags>
      </metadata>
    </package>

    Some organizations change the version field to denote that this is an edited internal package, for example changing 6.8.7 to 6.8.7.20151202. For now, this is not necessary.

  8. Now you can navigate via the command prompt to the folder with the .nuspec file (from a Windows machine unless you’ve installed Mono and built choco.exe from source) and use choco pack. You can also be more specific and run choco pack <FILE PATH>\notepadplusplus.commandline.nuspec. The output should be similar to below.
    Attempting to build package from 'notepadplusplus.commandline.nuspec'.
    Successfully created package 'notepadplusplus.commandline.6.8.7.nupkg'

    Normally you test on a system to ensure that the package you just built is good prior to pushing the package (just the *.nupkg) to your internal repository. This can be done by using choco.exe on a test system to install (choco install notepadplusplus.commandline -source %cd% - change %cd% to $pwd in PowerShell.exe) and uninstall (choco uninstall notepadplusplus.commandline). Another method of testing is to run the manifest pointed to a local source folder, which is what you are going to do.

  9. Create c:\packages and copy the resulting package file (notepadplusplus.commandline.6.8.7.nupkg) into it.
    This won’t actually install on this system since you just installed the same version from Chocolatey’s community feed. So you need to remove the existing package first. To remove it, edit your chocolatey.pp to set the package to absent.
    package {'notepadplusplus.commandline':
      ensure   => absent,
      provider => chocolatey,
    }
  10. Validate the manifest with puppet parser validate path\to\chocolatey.pp. Apply the manifest to ensure the change puppet apply c:\path\to\chocolatey.pp.

    You can validate that the package has been removed by checking for it in the package install location or by using choco list -lo.

  11. Update the manifest (chocolatey.pp) to use the custom package.
    package {'notepadplusplus.commandline':
      ensure   => latest,
     provider => chocolatey,
     source   => 'c:\packages',
    }
  12. Validate the manifest with the parser and then apply it again. You can see Puppet creating the new install in the output.
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.79 seconds
    Notice: /Stage[main]/Main/Package[notepadplusplus.commandline]/ensure: created
    Notice: Applied catalog in 14.78 seconds
  13. In an earlier step, you added a *.zip file to the package, so that you can inspect it and be sure the custom package was installed. Navigate to C:\ProgramData\chocolatey\lib\notepadplusplus.commandline\tools (if you have a default install location for Chocolatey) and see if you can find the *.zip file.

    You can also validate the chocolateyInstall.ps1 by opening and viewing it to see that it is the custom file you changed.

Create a package with chocolatey

Creating your own packages is, for some system administrators, surprisingly simple compared to other packaging standards.

Ensure you have at least Chocolatey CLI (choco.exe) version 0.9.9.11 or newer for this next part.

  1. From the command prompt, enter choco new -h to see a help menu of what the available options are.
  2. Next, use choco new vagrant to create a package named 'vagrant'. The output should be similar to the following:
    Creating a new package specification at C:\temppackages\vagrant
       Generating template to a file
        at 'C:\temppackages\vagrant\vagrant.nuspec'
       Generating template to a file
        at 'C:\temppackages\vagrant\tools\chocolateyinstall.ps1'
       Generating template to a file
        at 'C:\temppackages\vagrant\tools\chocolateyuninstall.ps1'
       Generating template to a file
        at 'C:\temppackages\vagrant\tools\ReadMe.md'
       Successfully generated vagrant package specification files
        at 'C:\temppackages\vagrant'
    
    It comes with some files already templated for you to fill out (you can also create your own custom templates for later use).
  3. Open vagrant.nuspec, and edit it to look like this:
    <?xml version="1.0" encoding="utf-8"?>
    <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
      <metadata>
        <id>vagrant</id>
        <title>Vagrant (Install)</title>
        <version>1.8.4</version>
        <authors>HashiCorp</authors>
        <owners>my company</owners>
        <description>Vagrant - Development environments made easy.</description>
      </metadata>
      <files>
        <file src="tools\**" target="tools" />
      </files>
    </package>
    

    Unless you are sharing with the world, you don’t need most of what is in the nuspec template file, so only required items are included above. Match the version of the package in this nuspec file to the version of the underlying software as closely as possible. In this example, Vagrant 1.8.4 is being packaged.

  4. Open chocolateyInstall.ps1 and edit it to look like the following:
    $ErrorActionPreference = 'Stop';
    
    $packageName= 'vagrant'
    $toolsDir   = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
    $fileLocation = Join-Path $toolsDir 'vagrant_1.8.4.msi'
    
    $packageArgs = @{
      packageName   = $packageName
      fileType      = 'msi'
      file         = $fileLocation
    
      silentArgs    = "/qn /norestart"
      validExitCodes= @(0, 3010, 1641)
    }
    
    Install-ChocolateyInstallPackage @packageArgs
    Note: The above is Install-ChocolateyINSTALLPackage, not to be confused with Install-ChocolateyPackage. The names are very close to each other, however the latter also downloads software from a URI (URL, ftp, file) which is not necessary for this example.
  5. Delete the ReadMe.md and chocolateyUninstall.ps1 files. Download Vagrant and move it to the tools folder of the package.
    Note: Normally if a package is over 100MB, it is recommended to move the software installer/archive to a share drive and point to it instead. For this example, just bundle it as is.
  6. Now pack it up by using choco pack. Copy the new vagrant.1.8.4.nupkg file to c:\packages.
  7. Open the manifest, and add the new package you just created. Your chocolatey.pp file should look like the below.
    package {'vagrant':
      ensure   => installed,
      provider => chocolatey,
      source   => 'c:\packages',
    }
  8. Save the file and make sure to validate with the Puppet parser.
  9. Use puppet apply <FILE PATH>\chocolatey.pp to run the manifest.
  10. Open Control Panel, Programs and Features and take a look.
Results

A screenshot of the Programs and Features window, with Vagrant listed as an installed program.

Vagrant is installed!

Uninstall packages with Chocolatey

In addition to installing and creating packages, Chocolatey can also help you uninstall them.

To verify that the choco autoUninstaller feature is turned on, use choco feature to list the features and their current state. If you're using include chocolatey or class chocolatey to ensure Chocolatey is installed, the configuration is applied automatically (unless you have explicitly disabled it). Starting in Chocolatey version 0.9.10, it is enabled by default.

  1. If you see autoUninstaller - [Disabled], you need to enable it. To do this, in the command prompt, run choco feature enable -n autoUninstaller You should see a similar success message:

    You should see a similar success message:

    Enabled autoUninstaller
  2. To remove Vagrant, edit your chocolatey.pp manifest to ensure => absent. Then save and validate the file.
    package {'vagrant':
      ensure   => absent,
      provider => chocolatey,
      source   => 'c:\packages',
    }
  3. Next, run puppet apply <FILE PATH>\chocolatey.pp to apply the manifest.
    Notice: Compiled catalog for win2012r2x64 in environment production in 0.75 seconds
    Notice: /Stage[main]/Main/Package[vagrant]/ensure: removed
    Notice: Applied catalog in 40.85 seconds
Results

You can look in the Control Panel, Programs and Features to see that it’s no longer installed!


A screenshot of the Programs and Features windows showing that Vagrant is no longer in the list of installed programs.