Windows Server Automation with PowerShell Cookbook

Copyright © 2022 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Early Access Publication: Windows Server Automation with PowerShell Cookbook

Early Access Production Reference: B18878

Published by Packt Publishing Ltd.

Livery Place

35 Livery Street

Birmingham

B3 2PB, UK

ISBN: 978-1-80461-423-5

www.packt.com

Table of Contents

Windows Server Automation with PowerShell Cookbook, Fifth Edition: Powerful ways to automate and manage Windows Server 2022 administrative tasks

1 Installing and Configuring PowerShell 7

Feedback

Introduction

Systems used in the chapter

Installing PowerShell 7

Getting ready

How to do it...

How it works...

There's more...

Installing PowerShell 7 Using Chocolatey

Getting ready

How to do it...

How it works...

There's more...

Using the PowerShell 7 console

Getting ready

How to do it...

How it works...

There's more...

Building PowerShell 7 profile files

Getting ready

How to do it...

How it works...

There's more...

Exploring installation artifacts

Getting ready

How to do it...

How it works...

There's more...

Installing VS Code

Getting ready

How to do it...

How it works...

There's more...

Installing the Cascadia Code font

How to do it...

How it works...

There's more...

Exploring PSReadline

How to do it...

How it works...

There's more...

2 Managing PowerShell 7 in the Enterprise

Feedback

Introduction

The system used in the chapter

Utilizing Windows PowerShell Compatibility

Getting ready

How to do it...

How it works...

There's more...

Installing RSAT Tools

Getting ready

How to do it...

How it works...

There's more...

Exploring Package Management

Getting ready

How to do it...

How it works...

There's more...

Exploring PowerShellGet and the PS Gallery

Getting ready

How to do it...

How it works...

There's more...

Creating and using a local package repository

Getting ready

How to do it...

How it works...

There's more...

Establishing a script signing environment

Getting ready

How to do it...

How it works...

There's more...

Working with shortcuts and the PSShortCut module

Getting ready

How to do it...

How it works...

There's more...

Working with Archive files

Getting ready

How to do it...

How it works...

There's more...

Searching for files using the Everything search tool

Getting ready

How to do it...

How it works...

There's more...

3 Exploring .NET

Feedback

Introduction

The system used in the chapter

Exploring .NET Assemblies

Getting ready

How to do it...

How it works...

There's more...

Exploring .NET Classes

Getting ready

How to do it...

How it works...

There's more...

Exploring .NET Methods

Getting ready

How to do it...

How it works...

There's more...

Creating a C# Extension

Getting ready

How to do it...

How it works...

There's more...

Creating a cmdlet

Getting ready

How to do it...

How it works...

There's more...

4 Managing Active Directory

Feedback

Introduction

The systems used in the chapter

Installing a Forest Root Domain

Getting ready

How to do it...

How it works...

There's more...

Testing an AD installation

Getting ready

How to do it...

How it works...

There's more...

Installing a replica Domain Controller

Getting ready

How to do it...

How it works...

There's more...

Installing a Child domain

Getting ready

How to do it...

How it works...

There's more...

Creating and Managing AD Users and groups

Getting ready

How to do it...

How it works...

There's more...

Managing AD computers

Getting ready

How to do it...

How it works...

There's more...

Adding/Removing users using a CSV file

Getting ready

How to do it...

How it works...

There's more...

Creating Group Policy Objects

Getting ready

How to do it...

How it works...

There's more...

Reporting on AD Users

Getting ready

How to do it...

How it works...

There's more...

Reporting on AD Computers

Getting ready

How to do it...

How it works...

There's more...

Managing AD Replication

Getting Ready

How to do it…

How it works…

There’s more…

Cover

Table of contents

Windows Server Automation with PowerShell Cookbook, Fifth Edition: Powerful ways to automate and manage Windows Server 2022 administrative tasks

Welcome to Packt Early Access. We’re giving you an exclusive preview of this book before it goes on sale. It can take many months to write a book, but our authors have cutting-edge information to share with you today. Early Access gives you an insight into the latest developments by making chapter drafts available. The chapters may be a little rough around the edges right now, but our authors will update them over time.

You can dip in and out of this book or follow along from start to finish; Early Access is designed to be flexible. We hope you enjoy getting to know more about the process of writing a Packt book.

Chapter 1: Installing and Configuring PowerShell 7

Chapter 2: Managing PowerShell in the Enterprise

Chapter 3: Exploring .NET

Chapter 4: Managing Networking

Chapter 5: Managing AD

Chapter 6: Implementing Enterprise Security

Chapter 7: Managing Storage

Chapter 8: Managing Shared Data

Chapter 9: Managing Printing

Chapter 10: Exploring Windows Containers

Chapter 11: Managing Hyper-V

Chapter 12: Managing Azure

Chapter 13: Debugging and Troubleshooting Windows Server

Chapter 14: Managing Window Server with WMI

Chapter 15: Managing Windows Updates

1 Installing and Configuring PowerShell 7

Feedback

We are constantly looking at improving our content, so what could be better than listening to what you as a reader have to say? Your feedback is important to us and we will do our best to incorporate it. Could you take two mins to fill out the feedback form for this book and let us know what your thoughts are about it? Here's the link: https://packt.link/A8yX4.

Thank you in advance.

This chapter covers the following recipes:

Installing PowerShell 7

Installing PowerShell 7 using Chocolately

Using the PowerShell 7 Console

Building PowerShell 7 profile files

Exploring Installation artifacts

Installing VS Code

Installing the Cascadia Code font

Exploring PSReadLine

Introduction

Since moving to Open Source, PowerShell Core or PowerShell 7, or just plain PowerShell, has been a continuing work in progress. Windows PowerShell, first introduced to the public in 2003, was first released formally, as Windows PowerShell v1, in 2006. Over the next decade, Microsoft released multiple versions of Windows PowerShell, ending with PowerShell 5.1. Additionally, Windows PowerShell changed from an add-in to Windows to an integrated feature of Windows. Microsoft plans to support Windows PowerShell 5.1 for a long time, but no new features are likely.

In 2016, the PowerShell development team began working on an open-source version of PowerShell based on the open-source version of .NET Core. You can read the announcement by Jeffrey Snover here: https://azure.microsoft.com/en-us/blog/powershell-is-open-sourced-and-is-available-on-linux/.

The initial versions, PowerShell Core 6.0, 6.1, and 6.2, represented, in effect, a proof of concept – you could run the core functions and features of PowerShell across the Windows, Mac, and Linux platforms. Those early versions also enabled the development team to implement all the necessary tooling to allow future development. But they were quite limited in supporting the rich needs of the IT professional community.

With the release of PowerShell 7.0 came improved parity with Windows PowerShell. A few modules did not work with PowerShell 7, and a few more operated via a compatibility mechanism. PowerShell 7.0 shipped in 2019 and was followed by version 7.1 and Version 7.2 (released in late 2021). This book uses the term “PowerShell 7” to include both PowerShell 7.0, 7.1, and 7.2. If there are version-specific issues, the chapters call those out specifically.

Because Microsoft does not include PowerShell 7 in Windows, you have to install it on each system. And as ever, you have options including direct from GitHub and via other installers such as Chocolatey.

Once you have installed PowerShell 7, you can use it just as you used the Windows PowerShell console to run commands or scripts. You can run it from a shortcut on the desktop, from the start panel, from a shortcut on the taskbar, or just run the executable. The name of the executable for PowerShell 7 is pwsh.exe (versus powershell.exe for Windows PowerShell).

Another important difference is that PowerShell 7 uses different profile file locations from Windows PowerShell. This feature allows you to customize your profiles to use the new PowerShell 7 features. And that, in turn, enables you to run both Windows PowerShell and PowerShell 7 side by side without interference.

Most IT Pros who have used Windows PowerShell are familiar with the Integrated Scripting Environment (ISE). The ISE was a great tool you used with Windows PowerShell. However, you cannot use the ISE with PowerShell 7. A very worth successor to the ISE is Visual Studio Code (VS Code), an open-source editing project that provides all the features of the ISE and a great deal more. Installation of VS Code is optional but relatively straightforward.

Microsoft also developed a new font, Cascadia Code, to coincide with the launch of VS Code. This font is a nice improvement over Courier or other mono-width fonts. All screenshots of working code in this book use this new font.

PSReadLine is a PowerShell module designed to provide color-coding of PowerShell scripts in the PowerShell 7 console. The module, included with PowerShell 7 by default, makes editing at the command line easier and more on par with the features available in Linux shells. You can also use the later versions of PS Readline with Windows PowerShell.

Systems used in the chapter

This chapter is all about getting you started with PowerShell 7 – installing and configuring your environment to make the most out of PowerShell 7. In this chapter, you use a single host, SRV1, as follows:

Figure 1.1: Host in use for this chapter

In later chapters, you will use additional servers and will promote SRV1 to be a domain-based server rather than being in a workgroup.

Installing PowerShell 7

As mentioned, PowerShell 7 is not installed in Windows by default, at least not at the time of writing. The PowerShell team made PowerShell 7.1 available from the Microsoft Store, which is useful to install PowerShell 7.1 or later on Windows 10/11 systems. Windows Server does not6 support the Microsoft store.

You have other methods of installing PowerShell 7 on your systems. The first option is to use the Install-PowerShell.ps1, which you download from GitHub, as shown in this recipe. You can also use this recipe on Windows 10 hosts. This approach has the advantage of being the most up-to-date source of the latest versions of PowerShell.

Getting ready

This recipe uses SRV1, a Windows Server workgroup host. There are no features of applications loaded on this server (yet).

You can use either the Windows PowerShell console or the ISE for this recipe.

How to do it...

Setting an execution policy for Windows PowerShell

Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force

Updating help text for Windows PowerShell

Update-Help -Force |
Out-Null

Ensuring the C:\Foo Folder exists

$LFHT = @{
  ItemType    = 'Directory'
  ErrorAction = 'SilentlyContinue' # should it already exist
}
New-Item -Path C:\Foo @LFHT | Out-Null

Downloading PowerShell 7 installation script from GitHub

Set-Location -Path C:\Foo
$URI = 'https://aka.ms/install-powershell.ps1'
Invoke-RestMethod -Uri $URI |
  Out-File -FilePath C:\Foo\Install-PowerShell.ps1

Viewing Installation Script Help

Get-Help -Name C:\Foo\Install-PowerShell.ps1

Installing PowerShell 7.2

$EXTHT = @{
  UseMSI                 = $true
  Quiet                  = $true
  AddExplorerContextMenu = $true
  EnablePSRemoting       = $true
}
C:\Foo\Install-PowerShell.ps1 @EXTHT | Out-Null

Installing the preview and daily builds (for the adventurous)

C:\Foo\Install-PowerShell.ps1 -Preview -Destination C:\PSPreview |
  Out-Null
C:\Foo\Install-PowerShell.ps1 -Daily   -Destination C:\PSDailyBuild |
  Out-Null

Creating Windows PowerShell default profiles

#    First the ISE
$URI = 'https://raw.githubusercontent.com/doctordns/PACKTPS72/master/' +
       '/scripts/goodies/Microsoft.PowerShell_Profile.ps1'
$ProfileFile    = $Profile.CurrentUserCurrentHost
New-Item $ProfileFile -Force -WarningAction SilentlyContinue |
   Out-Null
(Invoke-WebRequest -Uri $URI -UseBasicParsing).Content |
  Out-File -FilePath  $ProfileFile
# Now profile for ConsoleHost
$ProfilePath    = Split-Path -Path $ProfileFile
$ChildPath      = 'Microsoft.PowerShell_profile.ps1'
$ConsoleProfile = Join-Path -Path $ProfilePath -ChildPath $ChildPath
(Invoke-WebRequest -Uri $URI -UseBasicParsing).Content |
  Out-File -FilePath  $ConsoleProfile

Checking versions of PowerShell 7 loaded

Get-ChildItem -Path C:\pwsh.exe -Recurse -ErrorAction SilentlyContinue

How it works...

In step 1, you set the execution policy for Windows PowerShell to Unrestricted. This step, which produces no output, simplifies the installation and setup of PowerShell. In production, you may wish to set PowerShell’s execution policy to be more restrictive.

In step 2, you update the help text files for Windows PowerShell, which produces output like this:

Figure 1.2: Updating Help Files

Note that after installing PowerShell 7, PowerShell prompts you to download help text (not shown in this figure) the first time you use Get-Help.

In step 3, you create a folder, C:\Foo. This book uses this folder as a place to put files used by the book’s recipes. For example, this recipe stores the PowerShell installation file in this folder from which you execute the script to install PowerShell 7.

With step 4, you download the PowerShell installation script from GitHub. Although you can look in C:\Foo to examine the script, this step produces no output.

The installation script is a PowerShell script. In step 5, you use Get-Help to get details on the script, as shown here:

Figure 1.3: Getting help information from the installation script6

In step 6, you use the installation script to install PowerShell 7 on SRV1, with output like this:

Figure 1.4: Installing PowerShell 7

PowerShell 7 is a work in progress. On most weekdays, the PowerShell team builds updated versions of PowerShell. Monthly, the team also releases preview versions of the next major version. As of today, the current preview is 7.3 Preview 3 – but that should change by the time you read this and the team releases new previews. The daily and preview builds are usually very stable and allow you to try out new features which may be in the next major release. The daily build enables you to view progress on a specific bug or feature. You may find it useful to install both of these. Note that if you install preview/daily builds as shown in this recipe, you also need to ensure you keep them up to date as time goes by – Microsoft update does not update these side-by-side versions.

In step 7, you install the daily build and the latest preview build, which looks like this:

Figure 1.5: Installing the preview and daily builds

PowerShell, like Windows PowerShell, uses profile files to enable you to configure PowerShell each time you run it (whether in the PowerShell console or as part of VS Code.

In step 8, you download sample PowerShell profile scripts and save them locally, which produces no output. This step assumes you are running the script from the ISE – the first part creates an ISE profile while the second establishes a PowerShell profile for the Console Host.

The executable name for PowerShell 7 is pwsh.exe. In step 9, you view the versions of this file as follows:

Figure 1.6: Checking PowerShell 7 versions loaded

As you can see, there are three versions of PowerShell 7 installed on SRV1: the latest full release, the latest preview, and the build of the day.

There's more...

In step 1, you update the execution policy for Windows PowerShell. While this simplifies the installation and configuration of hosts, it may be unduly permissive for your environment, and you can change it as needed. Don’t forget, though. PowerShell’s execution policy is not truly a security mechanism – it just slows down an inexperienced administrator. For a good explanation of PowerShell’s Security Guiding Principles, see https://devblogs.microsoft.com/powershell/powershells-security-guiding-principles/.

In step 2, you updated the help files for Windows PowerShell. This step is optional, but later steps can prompt you to update your help files if you skip it. Installing the most up-to-date help files also add many conceptual help topics to help you get more out of PowerShell.

In step 4, you use a shortened URL to download the Install-PowerShell.ps1 script. When you use Invoke-RestMethod, PowerShell discovers the underlying target URL for the script. The short URL allows Microsoft and the PowerShell team to publish a well-known URL and then have the flexibility to move the target location should that be necessary. The target URL, at the time of writing, is https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.ps1.

In step 6, you use the installation script and install PowerShell 7. This step installs PowerShell 7.2.2, as you can see, using an MSI. The MSI, which you install silently without any user notification) updates the system execution path to add the PowerShell 7 installation folder. At the time of writing, the latest released version of PowerShell is 7.2.2. The code here retrieves the latest supported version of PowerShell 7, which may be later than you see in the output here.

In step 8, you create some sample profile files. This step creates an ISE profile and a profile for the console host. Windows PowerShell uses the profile when you launch the PowerShell console. PowerShell 7 also uses the profile (when you run PowerShell 7 in the console or Microsoft Terminal).

In step 9, you can see that you have installed PowerShell 7 (into C:\Program Files) and the latest daily build and preview versions. The specific file versions you see may differ from the output shown here, reflecting the relentless progress of the PowerShell team.

Installing PowerShell 7 Using Chocolatey

Chocolatey is a third-party package management tool for Windows. Chocolately has a large set of packages you can install, and the Chocolatey tool (choco.exe) provides a rich set of management features. You can install Chocolatey on both Windows Client machines (Windows 10/11 and earlier versions). As this recipe demonstrates, you can also install Chocolatey on Windows Server.

Chocolatey has a very large online registry of Windows packages that you can install, simplifying the deployment of applications in your environment. Read more about the company and its products at its website https://chocolatey.org/.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7. The method shown here installs PowerShell 7 using an MSI package. In the Installing PowerShell 7 recipe, you installed PowerShell 7 using the MSI. With PowerShell already installed, this recipe installs Chocolately but would fail gracefully attempting to re-install PowerShell 7. If you want to test the installation of PowerShell 7 using Chocolately, you should remove PowerShell 7.

Run this script using the PowerShell ISE.

How to do it...

Downloading the installation script for Chocolatey

$ChocoIns = 'C:\Foo\Install-Chocolatey.ps1'
$DL       = New-Object System.Net.WebClient
$DL.DownloadString('https://community.chocolatey.org/install.ps1') |
  Out-File -FilePath $ChocoIns

Viewing the installation help file

C:\Foo\Install-Chocolatey.ps1 -?

Installing Chocolatey

C:\Foo\Install-Chocolatey.ps1

Configuring Chocolatey

choco feature enable -n allowGlobalConfirmation

Finding PowerShell (PWSH) on Chocolatey

choco find pwsh

Installing PowerShell 7 using coco.exe

choco install powershell-core –force

How it works...

In step 1, you download the Chocolately installation script. You need this script to install Chocolatey. This step produces no output.

In step 2, you use Get-Help to view the help information for the Chocolatey install script, with output like this:

Figure 1.7: Viewing the Chocolatey installation script help details

In step 3, you use the installation script to download and install Chocolatey on SRV1. The output looks like this:

Figure 1.8: installing Chocolatey

In step 4, you use choco.exe to set certain feature options with the following output:

Figure 1.9: Setting Chocolatey global options

In step 5, you use choco.exe to find PowerShell packages which you can install using Chocolatey. The output looks like this:

Figure 1.10: Finding PowerShell on Chocolatey

In step 6, you install PowerShell 7 using Chocolatey. There is a lot of output, which looks like this:

Figure 1.11: Installing PowerShell 7

There's more...

In step 1, you open a new Windows PowerShell 7 console. Make sure you run the console as the local administrator.

In step 6, you install PowerShell 7 (7.2.2, as you can see in the output). The result shows the successful installation of PowerShell.

If you do not uninstall PowerShell 7, then when you run this step, you see different output indicating that you have already installed the product and thus, and so fail gracefully.

Using the PowerShell 7 console

Once you have installed PowerShell 7, you can explore the PowerShell 7 console irrespective of your installation method. In the main, the PowerShell 7 console is similar to the Windows PowerShell console, but you should notice a few differences.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7. You can install PowerShell 7 using the installation script (as in Installing PowerShell 7 recipe). Chocolatey (as in Installing PowerShell 7 using Chocolatey), or any other mechanism. You run this recipe in the PowerShell 7 console – pwsh.exe.

How to do it...

Viewing the PowerShell version

$PSVersionTable

Viewing the $Host variable

$Host

Looking at the PowerShell process (PWSH)

Get-Process -Id $PID |
  Format-Custom -Property MainModule -Depth 1

Looking at resource usage statistics

Get-Process -Id $PID |
  Format-List CPU,*Memory*

Updating the PowerShell 7 help files

$Before = Get-Help -Name about_*
Update-Help -Force | Out-Null
$After = Get-Help -Name about_*
$Delta = $After.Count - $Before.Count
"{0} Conceptual Help Files Added" -f $Delta

Determining available commands

Get-Command |
  Group-Object -Property CommandType

Examining the Path Variable

$env:path.split(';')

How it works...

In step 1, you view the PowerShell version information contained in $PSVersionTable, which produces output like this:

Figure 1.12: Viewing $PSVersionTable variable

In step 2, you view the contents of the $Host variable, which contains details of the PowerShell host (i.e., the PowerShell 7 console), which looks like this:

Figure 1.13: Viewing $Host

In step 3, you view the details of the PowerShell process (pwsh.exe) with output like this:

Figure 1.14: Viewing pwsh process

In step 4, you can observe the resources used by this process by using Get-Process and viewing the resource-related properties, with output like this:

Figure 1.15: Viewing pwsh resource usage

It is always useful to get the most up-to-date help files, which you can do using Update-Help. In step5, you update the PowerShell 7 help files and count the number of conceptual help files resulting from updating help. The output of this step looks like this:

Figure 1.16: Updating the PowerShell 7 help files

In step 6, you use Get-Command to determine the number of commands available to a newly installed version of PowerShell 7.2.2 (in this case!) on a freshly installed version of Windows Server 2022. The output looks like this:

Figure 1.17: Updating the PowerShell 7 help files

In the final step, step 7, you review the contents of the path environment variable, with output like this:

Figure 1.18:Viewing available commands in PowerShell 7

There's more...

In step 1, you examine the $PSVersion built-in variable. At the time of writing, the latest released version of PowerShell 7 is 7.2.2, as you can see in the output. However, when you run this step, you may discover you have installed a later version.

You run pwsh.exe to start PowerShell 7 via the console. PowerShell has a built-in variable, $PID, which holds the Windows process id for the current PowerShell console. This variable can be useful if you have multiple consoles open at one time. You can use Get-Process, as shown in step 2, specifying the process id, to get details of ‘this’ PowerShell process.

Building PowerShell 7 profile files

Profile files are PowerShell scripts that PowerShell runs at startup. They are easy to create and support a range of deployment scenarios. They enable you to customize your PowerShell environment. See this article on Microsoft’s PowerShell Community Blog for more details on PowerShell profile files: https://devblogs.microsoft.com/powershell-community/how-to-make-use-of-powershell-profile-files/.

In this recipe, you examine profile files, download a sample PowerShell profile file, and install it on SRV1. This profile is just for the console. In a later recipe, you install VS Code and create a VS Code-specific profile.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7. You should begin this recipe by opening up a PowerShell 7 console.

How to do it...

Discovering the profile file names

$ProfileFiles = $PROFILE |  Get-Member -MemberType NoteProperty
$ProfileFiles | Format-Table -Property Name, Definition

Checking for the existence of each PowerShell profile files

Foreach ($ProfileFile in $ProfileFiles){
  "Testing $($ProfileFile.Name)"
  $ProfilePath = $ProfileFile.Definition.split('=')[1]
  If (Test-Path $ProfilePath){
    "$($ProfileFile.Name) DOES EXIST"
    "At $ProfilePath"
  }
  Else {
    "$($ProfileFile.Name) DOES NOT EXIST"
  }
  ""
}

Discovering Current User/Current Host Profile

Creating a Current User/Current Host profile for PowerShell 7 console

$URI = 'https://raw.githubusercontent.com/doctordns/PacktPS72/master/' +
       'scripts/goodies/Microsoft.PowerShell_Profile.ps1'
New-Item $CUCHProfile -Force -WarningAction SilentlyContinue |
   Out-Null
(Invoke-WebRequest -Uri $URI).Content |
  Out-File -FilePath  $CUCHProfile

Exiting from PowerShell 7 console

Exit

Restarting the PowerShell 7 console and viewing the profile output at startup

Get-ChildItem -Path $PROFILE

How it works...

In step 1, you use the $Profile built-in variable to obtain the file names of the four profile files in PowerShell, with output like this:

Figure 1.19: Obtaining PowerShell Profile files names

In step 2, you check to see which, if any, of the four profiles exist, with output like this:

Figure 1.20: Checking for the existence of profile files

The profile file most IT Pros uses is the Current User/Current Host profile (aka $Profile). In step 3, you discover the file name for this profile file, with the following output:

Figure 1.21: Viewing the name of the Current User/Current Host profile file

In step 4, you download a sample PowerShell console profile file from GitHub. This step creates no output. After making a new profile file, in step 5, you exit PowerShell 7. After restarting the console, in step 6, you view the details of this profile file. The output from this step looks like this:

Figure 1.22: Viewing the name of the Current User/Current Host profile file

There's more...

In step 1, you view the built-in profile file names. As you can see, PowerShell has four profile files you can use. These files enable you to configure a given PowerShell host or all hosts for one or all users. As you can see in step 2, none of the four profile files exist by default.

In step 4, you create the current user/current host profile file based on a code sample you download from GitHub. This profile file is a starting point and demonstrates many things you can do in a profile file.

In step 6, you view the profile file you created earlier. Also, notice that the prompt has changed – the current working directory when you start PowerShell is now C:\Foo.

Exploring installation artifacts

Installing PowerShell 7 creates some artifacts that may be useful to understand better how PowerShell 7 and Windows PowerShell differ. The installation folder for PowerShell, as well as the folders holding PowerShell modules, is different from Windows PowerShell

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and have added a profile file.

How to do it...

Checking the version table for PowerShell 7 console

$PSVersionTable

Examining the PowerShell 7 installation folder

Get-ChildItem -Path $env:ProgramFiles\PowerShell\7 -Recurse |
  Measure-Object -Property Length -Sum

Viewing PowerShell 7 configuration JSON file

Get-ChildItem -Path $env:ProgramFiles\PowerShell\7\powershell*.json |
  Get-Content

Checking initial Execution Policy for PowerShell 7

Get-ExecutionPolicy

Viewing module folders

$I = 0
$ModPath = $env:PSModulePath -split ';'
$ModPath |
  Foreach-Object {
    "[{0:N0}]   {1}" -f $I++, $_
  }

Checking the modules

$TotalCommands = 0
Foreach ($Path in $ModPath){
  Try { $Modules = Get-ChildItem -Path $Path -Directory -ErrorAction Stop
        "Checking Module Path:  [$Path]"
  }
  Catch [System.Management.Automation.ItemNotFoundException] {
    "Module path [$path] DOES NOT EXIST ON $(hostname)"
  }
  $TotalCommands = 0
  Foreach ($Module in $Modules) {
    $Cmds = Get-Command -Module ($Module.name)
    $TotalCommands += $Cmds.Count
  }
}

Viewing totals of commands and modules

$Mods = (Get-Module * -ListAvailable | Measure-Object).count
"{0} modules providing {1} commands" -f $Mods,$TotalCommands

How it works...

In step 1, you start the PowerShell 7 console on SRV1. The console should look like this:

Figure 1.23: Checking PowerShell Version information

In step 2, you use Measure-Object to determine how many files exist in the PowertShll installation folder and how much space those files occupy on the disk. The output of this step looks like this:

Figure 1.24: Examining the PowerShell 7 installation folder

PowerShell 7 uses the PWSH.JSON file (in the installation folder) to hold certain key settings. In step 3, you examine this file for PowerShell 7.2.2, with output like this:

Figure 1.25: Viewing the PWSH.JSON configuration file

In step 4, you check the current execution policy for PowerShell 7, with output as follows;

Figure 1.26: Checking PowerShell 7 execution policy

PowerShell 7, by default, loads modules from a series of folders as described in the PowerShell variable $PSModulepath variable. In step 5, you display the default module folders for PowerShell 7, with output like this:

Figure 1.27: Viewing the module folders for PowerShell 7

In step 6, you look at the modules in each module path and determine the total number of commands available via these modules. The output from this step looks like this:

Figure 1.28: Counting commands available

In the final step in this recipe, step 7, you view the number of modules and commands provided in those modules, with output like this:

Figure 1.29: Counting commands available

There's more...

In step 1, you open a new Windows PowerShell console and view the PowerShell version details. In this case, this is version 7.2.2, although, by the time you read this, the PowerShell team may have created newer versions.

As you can see in step 2, the pwsh.json file contains both the execution policy and a list of modules that PowerShell 7 should never attempt to load (as these three modules are known not to work with PowerShell 7, even using the Windows PowerShell compatibility mechanism. For reasons best known to themselves, the owners of these modules have declined the opportunity to port them over to work with PowerShell 7, although that can and may change.

In step 4, you view the current PowerShell 7 execution policy. Note that this policy is independent of the Windows PowerShell execution policy.

Installing VS Code

The Windows PowerShell ISE was a great tool that Microsoft first introduced with Windows PowerShell v2 (and vastly improved with v3). This tool has reached feature completeness, and Microsoft has no plans for further development.

However, in its place is Visual Studio Code or VS Code. This open-source tool provides an extensive range of features for IT pros and others. For IT professionals, this should be your editor of choice. VS Code is highly customizable, as the range of extensions demonstrates. While there is a learning curve (as for any new product), VS Code contains all the features you found in the ISE and a lot more.

VS Code, and the available extensions, are works in progress and are constantly evolving and improving. Each new release brings additional features. A recent addition from Draw.IO, for example, is the ability to create diagrams directly in VS Code. Take a look at this post for more details on this diagram tool: https://tfl09.blogspot.com/2020/05/over-weekend-i-saw-tweet-announcing-new.html.

There is a wealth of extensions you might be able to use, depending on your workload. For more details on VS Code, see https://code.visualstudio.com/. And for more information on the many VS Code extensions, you might be able to use: https://code.visualstudio.com/docs/editor/extension-gallery#:~:text=You%20can%20browse%20and%20install,on%20the%20VS%20Code%20Marketplace.

For many IT Pros using PowerShell 7, an important extension is the PowerShell Integrated Console. This extension implements a separate PowerShell host, the Visual Studio Code Host. This extension implements the same four profile file files which you can view. However, the AllUsers/This host and Current User/This Host profile files have the name Microsoft.VSCode_profile.ps1. These profile files mean you can use PowerShell Console host (and its associated profile files) by default but use the VS Code Host when you edit PowerShell files. It is easy to get confused at first. Remember that you only see the VS Code host by default when you open a PowerShell file.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7. You run the first part of this recipe in the PowerShell 7 console. Once you complete installing VS Code, you do the remainder of this recipe using VS Code.

How to do it...

Downloading the VS Code installation script from PS Gallery

$VSCPATH = 'C:\Foo'
Save-Script -Name Install-VSCode -Path $VSCPATH
Set-Location -Path $VSCPATH

Reviewing the installation help details

Get-Help -Name C:\Foo\Install-VSCode.ps1

Running the installation script and adding in some popular extensions

$Extensions =  'Streetsidesoftware.code-spell-checker',
               'yzhang.markdown-all-in-one',
               'hediet.vscode-drawio'
$InstallHT = @{
  BuildEdition         = 'Stable-System'
  AdditionalExtensions = $Extensions
  LaunchWhenDone       = $true
}
.\Install-VSCode.ps1 @InstallHT | Out-Null

Exiting from VS Code by clicking on File/Exit

Restarting VS Code as an Administrator

Click on Start, type code, and hit return.

Opening a VS Code Terminal and running PowerShell 7 as administrator.

Use the Terminal menu to open a new terminal.

Creating a profile file for VS Code

$SAMPLE =
  'https://raw.githubusercontent.com/doctordns/PACKT-PS7/master/' +
  'scripts/goodies/Microsoft.VSCode_profile.ps1'
(Invoke-WebRequest -Uri $Sample).Content |
  Out-File $Profile

Updating local user settings for VS Code

$JSON = @'
{
  "workbench.colorTheme": "Visual Studio Light",
  "powershell.codeFormatting.useCorrectCasing": true,
  "files.autoSave": "onWindowChange",
  "files.defaultLanguage": "powershell",
  "editor.fontFamily": "'Cascadia Code',Consolas,'Courier New'",
  "workbench.editor.highlightModifiedTabs": true,
  "window.zoomLevel": 1
}
'@
$JHT = ConvertFrom-Json -InputObject $JSON -AsHashtable
$PWSH = "C:\\Program Files\\PowerShell\\7\\pwsh.exe"
$JHT += @{
  "terminal.integrated.shell.windows" = "$PWSH"
}
$Path = $Env:APPDATA
$CP   = '\Code\User\Settings.json'
$Settings = Join-Path  $Path -ChildPath $CP
$JHT |
  ConvertTo-Json  |
    Out-File -FilePath $Settings

Creating a shortcut to VSCode

$SourceFileLocation  = "$env:ProgramFiles\Microsoft VS Code\Code.exe"
$ShortcutLocation    = "C:\foo\vscode.lnk"
# Create a  new wscript.shell object
$WScriptShell        = New-Object -ComObject WScript.Shell
$Shortcut            = $WScriptShell.CreateShortcut($ShortcutLocation)
$Shortcut.TargetPath = $SourceFileLocation
# Save the Shortcut to the TargetPath
$Shortcut.Save()

Creating a shortcut to PowerShell 7

$SourceFileLocation  = "$env:ProgramFiles\PowerShell\7\pwsh.exe"
$ShortcutLocation    = 'C:\Foo\pwsh.lnk'
# Create a  new wscript.shell object
$WScriptShell        = New-Object -ComObject WScript.Shell
$Shortcut            = $WScriptShell.CreateShortcut($ShortcutLocation)
$Shortcut.TargetPath = $SourceFileLocation
#Save the Shortcut to the TargetPath
$Shortcut.Save()

Building an updated Layout XML file

$XML = @'
<?xml version="1.0" encoding="utf-8"?>
<LayoutModificationTemplate
  xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
  xmlns:defaultlayout=
    "http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
  xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
  xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
  Version="1">
<CustomTaskbarLayoutCollection>
<defaultlayout:TaskbarLayout>
<taskbar:TaskbarPinList>
 <taskbar:DesktopApp DesktopApplicationLinkPath="C:\Foo\vscode.lnk" />
 <taskbar:DesktopApp DesktopApplicationLinkPath="C:\Foo\pwsh.lnk" />
</taskbar:TaskbarPinList>
</defaultlayout:TaskbarLayout>
</CustomTaskbarLayoutCollection>
</LayoutModificationTemplate>
'@
$XML | Out-File -FilePath C:\Foo\Layout.Xml

Importing the start layout XML file Note: You get an error if this is not run in an elevated session

Import-StartLayout -LayoutPath C:\Foo\Layout.Xml -MountPath C:\

Creating a profile file for PWSH 7 Consoles

$ProfileFolder = Join-Path ($Env:homeDrive+ $env:HOMEPATH) 'Documents\PowerShell'
$ProfileFile2   = 'Microsoft.PowerShell_Profile.ps1'
$ConsoleProfile = Join-Path -Path $ProfileFolder -ChildPath $ProfileFile2
New-Item $ConsoleProfile -Force -WarningAction SilentlyContinue |
   Out-Null
$URI2 = 'https://raw.githubusercontent.com/doctordns/PACKT-PS7/master/' +
        "scripts/goodies/$ProfileFile2"
(Invoke-WebRequest -Uri $URI2).Content |
  Out-File -FilePath  $ConsoleProfile

Logging off

logoff.exe

Logging into Windows to observe the updated taskbar

How it works...

In step 1, you get the VS Code installation script from the PS Gallery, which produces no output. In step 2, you view the help information from the Install-VSCode.ps1 script file, with output like this:

Figure 1.30: Viewing the Install-VSCode.ps1 help information

In step 3, you install VS Code, with output that looks like this:

Figure 1.31: The PowerShell 7 console

After installing VS Code, the installation script starts VS Code (as an ordinary user) with looks like this:

Figure 1.32: Initial VS Code window

In step 4, you exit out of this VS Code instance, and in step 5, you restart VS Code but run it as administrator. With these two steps, you open VS Code as an administrator.

In step 6, you use the VS Code GUI to create a new terminal, with output like this:

Figure 1.33: Running a new terminal

In the next steps, which generate no console output, you configure VS Code for your environment. In step 7, you create a new profile file specifically for the VS Code PowerShell extension. In step 8, you update the VS Code local user settings. In step 9, you create a shortcut to the VS Code executable (code.exe) and save it in C:\Foo. In step 10, you create a new shortcut for the PowerShell 7 console, which you also store in C:\Foo. In step 11, you build an XML file describing the Windows toolbar. In step 12, you import the new start bar layout (but note it does not take effect until you re-login). Then in step 13, you create profiles for the new PowerShell 7 console also based on the downloaded profile fine. Finally, in step 14, you log off SRV1.

In step 15, you re-login to windows as the local administrator. When you log in, you can see the new Windows toolbar, with shortcuts for the PowerShell 7 console and VS Code, like this:

Figure 1.34: Viewing the updated Windows toolbar

There's more...

In step 1, you open a new Windows PowerShell 7 console. Make sure you run the console as the local administrator

In step 15, you can see the updated Window toolbar with shortcuts to the Windows PowerShell console, VS Code, and the PowerShell 7 console.

Installing the Cascadia Code font

Fonts, like color schemes in general, are a very personal thing. Some people love the comic sans font, for example, while others loathe it. Some love dark themes, and others don’t. For programming, including PowerShell (with the console, VS Code, and possibly even notepad), fixed-width fonts are easier to use. But the choice of which font is always a personal preference.

As part of the Visual Studio Code launch, Microsoft also created a new and free font that you can download and use at the PowerShell 7 console and inside VS Code. This recipe shows how you can download the font, install it, and set this font to be the default in VS Code.

This recipe shows how to install the Cascadia Code font, but you can choose many great fonts. See this article on (arguably!) the ten best fonts for use with VS Code https://toastofcode.com/best-fonts-for-programming-with-vscode/.

And should you want to use a different font for VS Code, you can adapt this recipe to make use of whatever font you wish. Or use the VS Code settings menus to change the font as you want.

How to do it...

Getting download Locations

$CascadiaFont    = 'CascadiaCode'    # font file name
$CascadiaRelURL  = 'https://github.com/microsoft/cascadia-code/releases'
$CascadiaRelease = Invoke-WebRequest -Uri $CascadiaRelURL # Get all
$CascadiaPath    = ‘https://github.com’ + ($CascadiaRelease.Links.href |
                      Where-Object { $_ -match "$CascadiaFont" } |
                        Select-Object -First 1)
$CascadiaFile   = ‘C:\Foo\$CascadiaFont.zip’

Downloading the Cascadia Code font file archive

Invoke-WebRequest -Uri $CascadiaPath -OutFile $CascadiaFile

Expanding the font archive file

$CascadiaFontFolder = 'C:\Foo\CascadiaCode'
Expand-Archive -Path $CascadiaFile -DestinationPath $CascadiafontFolder

Installing the Cascadia Code font

$FontFile = '\Foo\CascadiaCode\ttf\CascadiaCode.ttf'
$FontShellApp = New-Object -Com Shell.Application
$FontShellNamespace = $FontShellApp.Namespace(0x14)
$FontShellNamespace.CopyHere($FontFile, 0x10)

How it works...

In step 1, you determine the location of the latest release of the font and set a variable to the location to download the font.

In step 2, you use Invoke-WebRequest to download the font archive. Then in step 3, you use Expand-Archive to expand the archive. Finally, in step 4, you install the Cascadia Font. The steps in this recipe produce no console output – but you can see the change in VS Code after you run these steps.

There's more...

In step 1, you determine the location of the latest release of the Cascadia Code font on GitHub. The font is heavily used and has been subject to minor updates and improvements over time. This step ensures you get the latest version. The remaining steps expand the downloaded archive and then install the font. Once you complete this recipe, you should observe the font inside VS Code.

In step 2, you download the latest version of the font – but as a ZIP archive which in step 3, you expand and then install (in step 4).

Exploring PSReadline

Early versions of PowerShell were monochrome, although the terminal (conhost.exe) did provide some limited coloring. These versions of Windows PowerShell also lacked some of the cool features found in Linux shells.

With PowerShell 4, PowerShell included a new module, PSReadLine. The module provides a command-line editing experience that is on a par with the best of the Linux command shells (e.g. BASH). The PSReadLine module provides additional console editing features within both PowerShell 7 and Windows PowerShell.

When you type into a PowerShell console, PSReadLine intercepts your keystrokes to provide syntax coloring, simple syntax error notification, etc. PSReadLine enables you to customize your environment to suit your personal preferences. Some key features of the module include:

Syntax coloring of the command line entries

Multi-line editing

History Management

Customizable key bindings

Highly customizable

For an overview of PSReadLine, see https://docs.microsoft.com/powershell/module/psreadline/about/about_psreadline. And for more details, you can view the PSReadLine’s GitHub README file: https://github.com/PowerShell/PSReadLine/blob/master/README.md.

There are several minor issues you may need to understand. One issue is the naming of this module. The original name of the module was PSReadline. At some point, the module’s developers changed the module's name to PSReadLine (capitalizing the L character in the module name). Unfortunately, that change caused Update-Help to fail since there is case sensitivity in module names). You can fix this by manually updating the module’s folder name from PSReadline to PSReadLine

Another issue arises if you use VS Code. The PSReadLine module ships natively with PowerShell 7. If you use VS Code’s PowerShell Integrated Terminal, you can not load any newer version of PSReadline. At least until the development team updates the PowerShell extension to utilize the updated version of PSReadLine. This is by design. For most IT Pros, this probably does not matter much. But you may find later versions of PSReadLine contains features you want – if so, you should be able to use the Preview (i.e., beta!) version of the PowerShell extension, which supports the latest version of PSReadLine.

A final issue relates to changes made at V2. With the module’s V2 release, the dev team made some changes that were not backward compatible. But be aware that some older scripts may need adjusting. Many blog articles, for example, use the older V1 syntax for Set‑PSReadLineOption which fails with later versions of the module. You may still see the old syntax if you use your search engine to discover examples. Likewise, some of the examples in this recipe fail should you run them utilizing PSReadline V1. Over time, though, the documentation and blog posts should catch up.

You run this recipe on SRV1 after you have installed PowerShell 7. Run this recipe in VS Code after configuring VS Code and loading the Cascadia Code font.

How to do it...

Getting commands in the PSReadline module

Get-Command -Module PSReadLine

Getting the first 10 PSReadLine key handlers

Get-PSReadLineKeyHandler |
  Select-Object -First 10
    Sort-Object -Property Key |
      Format-Table -Property Key, Function, Description

Discovering a count of unbound key handlers

$Unbound = (Get-PSReadLineKeyHandler -Unbound).count
"$Unbound unbound key handlers"

Getting the PSReadline options

Get-PSReadLineOption

Determining the VS Code theme name

$Path       = $Env:APPDATA
$CP         = '\Code\User\Settings.json'
$JsonConfig = Join-Path  $Path -ChildPath $CP
$ConfigJSON = Get-Content $JsonConfig
$Theme = $ConfigJson |
           ConvertFrom-Json |
             Select-Object -ExpandProperty 'workbench.colorTheme'

Changing the VS Code colors

If ($Theme -eq 'Visual Studio Light') {
  Set-PSReadLineOption -Colors @{
    Member    = "`e[33m"
    Number    = "`e[34m"
    Parameter = "`e[35m"
    Command   = "`e[34m"
  }
}

How it works...

In step 1, you use Get-Command to discover commands in the PSReadLine module, with

Figure 1.35: Discovering commands in the PSReadLine module

In step 2, you use Get-PSReadLineKeyHandler to discover some of the key handlers implemented by PSReadline, with output like this:

Figure 1.36: Viewing Ten PSReadLine key handlers

In step 3, you calculate how many key handers are unbound and are available for you to use. The output from this step is:

Figure 1.37: Counting unbound key handlers

PSReadLine has many options you can set. In step 4, you use the Get-PSReadLineOption command to view the option settings, with output like this:

Figure 1.38: Counting unbound key handlers

In step 5, you determine the current VS Code theme, and in step 6, you change the PowerShell token colors, but only if the theme set is Visual Studio Light theme. These two steps produce no console output.

There's more...

In step 1, you open a new Windows PowerShell console. Make sure you run the console as the local administrator

In step 3, you view the first ten of the PSReadLine’s key handlers. Using PowerShell, PSReadLine captures specific keyboard sequences (e.g., Alt+L) and uses an assigned key handler to carry out some action. Typing Alt+L clears the terminal window (in VS Code and the PowerShell console). PSReadline implements a range of key handers with plenty of room for you to customize the editing experience and provide significant customization of the shell. As you can see in step 3, you can use over 100 key combinations to implement your own customizations.

The screenshots throughout most of this book use this color theme. You could extend your profile files to update token colors each time you start a VS Code terminal.

2 Managing PowerShell 7 in the Enterprise

Feedback

We are constantly looking at improving our content, so what could be better than listening to what you as a reader have to say? Your feedback is important to us and we will do our best to incorporate it. Could you take two mins to fill out the feedback form for this book and let us know what your thoughts are about it? Here's the link: https://packt.link/A8yX4.

Thank you in advance.

This chapter covers the following recipes:

Utilizing Windows PowerShell compatibility

Installing RSAT Tools

Exploring Package Management

Exploring PowerShellGet and the PS Gallery

Creating and using a local PowerShell repository

Establishing a script signing environment

Working with Shortcuts and PSShortCut module

Working with Archive files

Searching for files using Search Everything

Introduction

For an IT Professional in an Enterprise environment (and even in smaller ones), PowerShell 7 provides a platform to manage your environment. Many of the commands you need to carry out typical operations come with PowerShell 7, augmented by the commands provided with Windows Server feature tools. In addition, you can obtain third-party modules to extend your capabilities.

Automation, today, means using commands that come from many sources. Some are built-in, but there are gaps. Those gaps can be filled by modules you can obtain from the community and via the PowerShell gallery. In many cases, you can make use of .NET and WMI classes. When all else fails, there is often a command-line tool.

As you blend these tools into your workflow, you need to be aware of how a given set of commands work as well as the objects returned. You also need to deal with the mismatch. The Get‑ADComputer command, for example, returns the name of thae a computer in AD in the Name property. Most commands that interact with a given computer, use the parameter ComputerName.

In building Windows Server 2022, Microsoft did not update most of the role/feature management tools to support PowerShell 7. These tools work natively in Windows PowerShell but many do not work directly in PowerShell 7. The reason for this is that Microsoft based Windows PowerShell on Microsoft's .NET Framework, whereas PowerShell 7 is based on the open-source .NET. The .NET team did not port all the APIs from the .NET Framework into .NET. Thus many of the role/feature commands that you can run in Windows PowerShell do not run natively since .NET did not implement certain .NET Framework.

To get around this, PowerShell 7 comes with a Windows PowerShell compatibility solution. When you attempt to use some of the older commandsd (i.e. those that live in the System32 Windows directory), PowerShell 7 creates a PowerShell remoting session on the current host based on a Windows PowerShell 5.1 endpoint. Then, using implicit remoting, PowerShell creates and imports a script module of proxy functions that invoke the underlying commands in the remoting session. This enables you to use commands, such as Add-WindowsFeature, more or less seamlessly. You examine the Windows PowerShell compatibility feature in Utilizing Windows PowerShell Compatibility.

Most Windows Server 2022's features and roles provide management tools you use to manage the role or feature. You can also install and use these feature tools on any host allowing you to manage features across your network as you can see in Installing RSAT tools.

You can also find and utilize third-party modules that can improve your PowerShell experience. PowerShell implements a set of built-in package management commands, as you can see in Exploring Package Management.

Some organizations create their own private package repositories. These enable their organizations to share corporate modules inside the corporate network. You create a new package repository in the Creating and using a local package repository recipe.

PowerShell 7, like Windows PowerShell, supports the use of digitally signed scripts. You can configure PowerShell to run only properly signed scripts as a way of improving the organization's security. It is straightforward to establish a code‑signing environment, using self-signed certificates, as you investigate in the Establishing a code-signing environment recipe.

To simplify the use of commands in an interactive environment you may find it convenient to create shortcuts you can place on the desktop or in a folder of shortcuts. In Working with shortcuts and the PSShortCut module, you manage shortcuts.

A common problem that can face when automating your environment is the management of archive files. As you saw in Installing Cascadia Code font, you can often get components delivered as a ZIP file. In Working with Archive files, you examine commands for managing these files.

Windows, Windows Server, and Windows applications have grown significantly both in scope and the files that support them. Sometimes finding key files you need can be challenging, especially on larger file servers.

The system used in the chapter

This chapter is all about using PowerShell 7 in an enterprise environment and configuring your environment to make the most out of PowerShell 7. In this chapter, you use a single host, SRV1, as follows:

Figure 2.1: Host in use for this chapter

In later chapters, you will use additional servers and will promote SRV1 to be a domain-based server rather than being in a workgroup.

Utilizing Windows PowerShell Compatibility

The PowerShell 7 Windows Compatibility solution allows you to use older Windows PowerShell commands whose developers have not (yet) ported the commands to work natively in PowerShell 7. PowerShell 7 creates a special remoting session into a Windows PowerShell 5.1 endpoint, loads the modules into the remote session, then uses implicit remoting to expose proxy functions inside the PowerShell 7 session. This remoting session has a unique session name, WinPSCompatSession. Should you use multiple Windows PowerShell modules, PowerShell 7 loads them all into a single remoting session. Also, this session uses the "process" transport mechanism versus Windows Remote Management (WinRM). WinRM is the core transport protocol used with PowerShell remoting. The process transport is the transport used to run background jobs; it has less overhead than using WinRM, so is more efficient.

An example of the compatibility mechanism is using Get-WindowsFeature, a cmdlet inside the ServerManager module. You use the command to get details of features that are installed, or not, inside Windows Server. You use other commands in the ServerManager module to install and remove features. Unfortunately, the Windows Server team has not yet updated this module to work within PowerShell 7. Via the compatibility solution, the commands in the ServerManager module enable you to add, remove, and view features. The Windows PowerShell compatibility mechanism allows you to use existing Windows PowerShell scripts in PowerShell 7, although with some very minor caveats.

When you invoke commands in PowerShell 7, PowerShell uses its command discovery mechanism to determine which module contains your desired command. In this case, that module is the ServerManager Windows PowerShell module. PowerShell 7 then creates the remoting session and, using implicit remoting, imports the commands in the module as proxy functions. You then invoke the proxy functions to accomplish your goal. For the most part, this is totally transparent. You use the module's commands, and they return the object(s) you request. A minor caveat is that the compatibility mechanism does not import format XML for the Windows PowerShell module. The result is that the default output of some objects is not the same. There is a workaround for this, which is to manually install the Format XML.

With implicit remoting, PowerShell creates a function inside a PowerShell 7 session with the same name and parameters as the actual command (in the remote session). You can view the function definition in the Function drive (Get-Item Function:Get-WindowsFeature | Format-List -Property *). The output shows the proxy function definition that PowerShell 7 creates when it imports the remote module.

When you invoke the command by name, e.g. Get‑WindowsFeature, PowerShell runs the function. The function then invokes the remote cmdlet using the steppable pipeline. Implicit remoting is a complex feature that is virtually transparent in operation. You can read more about implicit remoting in https://www.techtutsonline.com/implicit-remoting-windows-powershell/.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Importing the ServerManager module

Import-Module -Name ServerManager

Viewing module details

Get-Module -Name ServerManager |
 Format-List

Displaying a Windows Feature

Get-WindowsFeature -Name 'TFTP-Client'

Running the same command in a remoting session

$Session = Get-PSSession -Name WinPSCompatSession
Invoke-Command -Session $Session -ScriptBlock {
  Get-WindowsFeature -Name 'TFTP-Client' |
  Format-Table
}

Getting the path to Windows PowerShell modules

$Paths = $env:PSModulePath -split ';'
$S32Path = $Paths |
  Where-Object {$_.ToString() -match 'system32'}
"System32 path: [$S32Path]"

Displaying path to the format XML for Server Manager module

$FXML = "$S32path/ServerManager"
$FF = Get-ChildItem -Path $FXML\*.format.ps1xml
"Format XML files:"
"   $($FF.Name)"

Updating format XML in PowerShell 7

Foreach ($F in $FF) {
  Update-FormatData -PrependPath $F.FullName}

Using the command with improved output

Get-WindowsFeature -Name TFTP-Client

How it works...

In step 1, you import the Server Manager module, with output like this:

Figure 2.2: Importing the Server Manager module

In step 2, you the Get-Module command to examine the Server Manager module's properties. The output of this step looks like this:

Figure 2.3: Importing the Server Manager module.

Now that you have loaded the Server Manager module, in step 3, you utilize one of the commands Get-WindowsFeature to view the details of a feature, with output like this;

Figure 2.4: Displaying the TFTP-Client feature

In step 4, by comparison, you run the same command inside the Windows PowerShell remoting session with slightly different results – like this:

Figure 2.5: Displaying the TFTP-Client feature inside the remoting session

With step 5, you determine and display the folder where Windows PowerShell modules can be found, with output like this:

Figure 2.6: Displaying the TFTP-Client feature inside the remoting sessiohn

In step 6, you find and display the Server Manager's display formatting XML file, with output like this:

Figure 2.7: Displaying the TFTP-Client feature inside the remoting session

In step 7, you import the display XML files – which produces no output. Finally, in step 8, you re-run the Get-WindowsFeature command. Since you have now imported the necessary display XML, you see output like this:

Figure 2.8: Displaying the TFTP-Client feature inside PowerShell 7 session

There's more...

In step 3, you used the Get-WindowsFeature to view one feature. When you run this command, PowerShell runs the command in the compatibility session and returns the object(s) for display in the PowerShell 7. However, as you may notice, the output is not the same as you would see, normally, in Windows PowerShell. This is because the Windows PowerShell compatibility does not, by default, add the formatting XML to the current PowerShell session.

Once you have loaded the Server Manager's display XML, as you can seed in step 8, running the Get-WindowsFeature command now produces the same nicely formatted output you can see in Windows PowerShell.

Installing RSAT Tools

The Remote Server Admin Tools (aka RSAT) is a set of management tools you use to manage individual Windows Features. The RSAT tools are fundamental to administering the roles and features you can install on Windows Server. The Windows Server DNS Server feature, for example, comes with both an MMC console and a module that you use to manage the DNS Server on a given host. A nice thing about the commands in the ServerManager module – everything is a feature, meaning you do not know whether the tools manage a Windows Feature or a Windows Role.

Each feature in Windows Server can optionally have management tools, and most do. These tools can include PowerShell cmdlets, functions, aliases, GUI Microsoft Management Console (MMC) files, and Win32 console applications. The DNS Server's RSAT tools include a PowerShell module, an MMC console as well as the command line tool dnscmd.exe. While you probably do not need the console applications since you can use the cmdlets, that is not always the case. And you may have some older batch scripts that use those console applications.

You can also install the RSAT tools independently of a Windows Server feature on Windows Server. This recipe covers RSAT tool installation on Windows Server 2019.

You can also install the RSAT tools in Windows 10 and administer your servers remotely. The specific method of installing the RSAT tools varies with the specific version of Windows 10 you are using. For earlier Windows 10 editions, you can download the tools here: https://www.microsoft.com/en-gb/download/details.aspx?id=45520.

In later editions of Windows 10, beginning with the Windows 10 October Update, you install the RSAT tools using the "Features on Demand" mechanism inside Windows 10. The URL in the previous paragraph has fuller details of how to install the RSAT tools on Windows 10.

As mentioned, PowerShell 7 is not installed in Windows by default, at least not at the time of writing. The PowerShell team made PowerShell 7.1 available from the Microsoft Store, which is useful to install PowerShell 7.1 or later on Windows 10/11 systems. Windows Server does not6 support the Microsoft store.

You have other methods of installing PowerShell 7 on your systems. The first option is to use the Install-PowerShell.ps1, which you download from GitHub, as shown in this recipe. You can also use this recipe on Windows 10 hosts. This approach has the advantage of being the most up-to-date source of the latest versions of PowerShell.

Getting ready

This recipe uses SRV1, a Windows Server workgroup host. There are no Windows features or server applications loaded on this server.

How to do it...

Displaying counts of available PowerShell commands

$CommandsBeforeRSAT = Get-Command
$CmdletsBeforeRSAT = $CommandsBeforeRSAT  |
  Where-Object CommandType -eq 'Cmdlet'
$CommandCountBeforeRSAT = $CommandsBeforeRSAT.Count
$CmdletCountBeforeRSAT  = $CmdletsBeforeRSAT.Count
"On Host: [$(hostname)]"
"Commands available before RSAT installed [$CommandCountBeforeRSAT]"
"Cmdlets available before RSAT installed  [$CmdletCountBeforeRSAT]"

Getting command types returned by Get-Command

$CommandsBeforeRSAT |
  Group-Object -Property CommandType

Checking the object type details

$CommandsBeforeRSAT |
  Get-Member |
  Select-Object -ExpandProperty TypeName -Unique

Getting the collection of PowerShell modules and a count of modules before adding the RSAT tools

$ModulesBefore = Get-Module -ListAvailable

Displaying a count of modules available before adding the RSAT tools

$CountOfModulesBeforeRSAT = $ModulesBefore.Count
"$CountOfModulesBeforeRSAT modules available"

Getting a count of features actually available on SRV1

Import-Module -Name ServerManager -WarningAction SilentlyContinue
$Features  = Get-WindowsFeature
$FeaturesI = $Features | Where-Object Installed
$RsatF   = $Features |
     Where-Object Name -Match 'RSAT'
$RSATFI  = $RSATF |
      Where-Object Installed

Displaying counts of features installed

"On Host [$(hostname)]"
"Total features available  [{0}]"  -f $Features.count
"Total features installed  [{0}]"  -f $FeaturesI.count
"Total RSAT features available [{0}]"  -f $RSATF.count
"Total RSAT features installed [{0}]"  -f $RSATFI.count

Adding all RSAT tools to SRV1

Get-WindowsFeature -Name *RSAT* |
  Install-WindowsFeature

Getting Details of RSAT tools now installed on SRV1

FSRV1A = Get-WindowsFeature
$IFSRV1A  = $FSRV1A | Where-Object Installed
$RSFSRV1A = $FSRV1A | Where-Object Installed |
      Where-Object Name -Match 'RSAT'

Displaying counts of commands after installing the RSAT tools

"After Installation of RSAT tools on SRV1"
"$($IFSRV1A.count) features installed on SRV1"
"$($RSFSRV1A.count) RSAT features installed on SRV1"

Displaying RSAT tools on SRV1

$MODS = "$env:windir\system32\windowspowerShell\v1.0\modules"
$SMMOD = "$MODS\ServerManager"
Update-FormatData -PrependPath "$SMMOD\*.format.ps1xml"
Get-WindowsFeature |
  Where-Object Name -Match 'RSAT'

Rebooting SRV1 then logging on as the local administrator

Restart-Computer -Force

How it works...

In step 1, you use Get-Command to obtain all the existing commands available on SRV1. Then you create a count of the commands available as well as a count of the PowerShell cmdlets available to you, with output like this:

Figure 2.9: Counting the available commands and cmdlets on SRV1

In step 2, you discover the different kinds of commands returned by Get-Command. The output looks like this:

Figure 2.10: Viewing command types returned by Get-Command

In PowerShell, the Get-Command cmdlet returns occurrences of different object types to describe the available commands. As you saw in the previous step, there are three command types returned by default by Get-Command. There is a fourth command type, Application, which Get-Command does not return by default. You can see the class names for those three command types in the output from step 3, which looks like this:

Figure 2.11: Determining full object type names

In step 4, you get the modules available on SRV1, which returns no console output. In step 5, you display a count of the modules discovered, with output like this:

Figure 2.12: Displaying the number of modules available

In step 6, you obtain a count of the features available on SRV1, which produces no output. In the following step, step 7, you display counts of the total features available and the number of RSAT tool sets available. The output from this step looks like this;

Figure 2.13: Displaying the number of features and RSAT features available

In step 8, you install all the available features onto SRV1, with output like this:

Figure 2.14: Adding all RSAT tools to SRV1

In step 9, you get details of all the features now available on SRV1, producing no output. In step 10, you obtain and display a count of the features, including RSAT features, on SRV1, with output like this:

Figure 2.15: Counting features and RSAT features

In step 11, you use Get-WindowsFeature to display all the RSAT tools and their installation status, with output like this:

Figure 2.16: Displaying RSAT features on SRV1

In the final step in this recipe, step 12, you reboot the host creating no console output.

There's more...

The output from step 1 shows there are 1809 total commands and 597 cmdlets available on SRV1, before adding the RSAT tools. The actual number may vary, depending on what additional tools, features, or applications you might have added to SRV1 or the specific Windows Server version.

In step 2 and step 3, you find the kinds of commands available and the object type name PowerShell uses to describe these different command types. When you have the class names, you can use your search engine to discover more details about each of these command types. There are two further types of command (ExternalScript and Application) but these are not returned by default.

In step 8, you install all RSAT tools and as you can see, Windows requires a reboot to complete the installation of the tools. Later, in step 12, you reboot SRV1 to complete the installation of these tools.

In step 11, you display all the RSAT tools using Get-WindowsFeature. Strictly speaking, this step displays the Windows features that have "RSAT" somewhere in the feature name. As you can see, there is no standard for actual feature names. The output shows the individual tools as either Feature Administration Tools or Role Administration tools although that difference is not relevant in your using the Server Manager cmdlets.

Exploring Package Management

The PackageManagement PowerShell module provides tools that enable you to download and install software packages from a variety of sources. The module, in effect, implements a provider interface that software package management systems use to manage software packages.

You can use the cmdlets in the PackageManagement module to work with a variety of package management systems. This module, in effect, provides an API to package management providers such as PowerShellGet, discussed in the "Exploring PowerShellGet and PS Gallery" recipe.

The primary function of the PackageManagement module is to manage the set of software repositories in which package management tools can search, obtain, install, and remove packages. The module enables you to discover and utilize software packages from a variety of sources, including the PowerShell Gallery.

The modules in the various Internet repositories, including the PowerShell Gallery, vary in quality. Some are excellent and are heavily used by the community, while others are less useful or of lower quality. Some are written by the PowerShell product team. Ensure you look carefully at any third-party module you put into production.

This recipe explores the PackageManagement module from SRV1.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Reviewing the cmdlets in the PackageManagement module

Get-Command -Module PackageManagement

Reviewing installed providers with Get-PackageProvider

Get-PackageProvider |
  Format-Table -Property Name,
         Version,
         SupportedFileExtensions,
         FromTrustedSource

Examining available Package Providers

$PROVIDERS = Find-PackageProvider
$PROVIDERS |
  Select-Object -Property Name,Summary |
  Format-Table -AutoSize -Wrap

Discovering and counting available packages

$PACKAGES = Find-Package
"Discovered {0:N0} packages" -f $PACKAGES.Count

Showing the first 5 packages discovered

$PACKAGES |
  Select-Object -First 5 |
  Format-Table -AutoSize -Wrap

Installing the ChocolateyGet provider

Install-PackageProvider -Name ChocolateyGet -Force |
  Out-Null

Verifying ChocolateyGet is in the list of installed providers

Import-PackageProvider -Name ChocolateyGet
Get-PackageProvider -ListAvailable |
  Select-Object -Property Name,Version

Discovering Packages using the ChocolateyGet provider

$CPackages = Find-Package -ProviderName ChocolateyGet -Name *
"$($CPackages.Count) packages available via ChocolateyGet"

How it works...

In step 1, you use Get-Command to discover the cmdlets in the PackageManagement module, with output like this:

Figure 2.17: The PowerShell 7 console

In step 2, you use the Get-PackageProvider cmdlet to view the package providers that you have loaded and imported, with output like this:

Figure 2.18: Reviewing installed package providers

In step 3, you use the Find-PackageProviders cmdlet to find additional package providers. The output looks like this:

Figure 2.19: Discovering additional package providers

You can use the Find-Package cmdlet to discover packages which you can download, install, and use. In step 4, you discover packages available with output that resembles this:

Figure 2.20: Counting available packages

In step 5, you view the details of five (the first five) packages you discovered in the prior step. The output looks like this:

Figure 2.21: Viewing package summaries

In step 6, you install a new package provider – the ChocolateyGet provider. This step produces no output. In step 7, you view the currently imported package providers, with output like this:

Figure 2.22: Viewing package summaries

In the final step in this recipe, step 8, you discover all the currently available packages you can download using the ChocolatelyGet package provider – the output looks like this:

Figure 2.23: Counting packages available via the ChocolateyGet provider

There's more...

In step 4, you display a count of the packages available in the PS Gallery. By the time you read this, the number of packages is certain to rise. Package authors in the community are constantly adding new and improved packages to the PS Gallery. At the same time, since some users may have developed some automation on top of older providers, these too remain in the PS Gallery.

In step 7, you install the ChocolateyGet packaged provider. With this provider, you can discover and install packages from the Chocolatey repository in addition to the PowerShell gallery. In Chapter 1, you used the Chocolatey command-line tool while in step 8, you find packages using the Find-Package cmdlet. The ChocolateyGet package provider is a wrapper around the command-line tool – and probably a lot easier to use for simple case. The PowerShell Gallery and Chocolatey have many packages in common so you can probably find whatever packages you might need in either or both.

Exploring PowerShellGet and the PS Gallery

In a perfect world, PowerShell would come with a command that performed every single action any IT Professional should ever need or want. But, as Jeffrey Snover (the inventor of PowerShell) says: To Ship is To Choose. And that means PowerShell itself, as well as Windows Server features, may not have every command you need. At a practical level the various Windows feature teams, as well as the PowerShell team do not have infinite resources to implement every good idea. And that is where the PowerShell community and the PowerShell Gallery (PS Gallery) comes in.

The PS Gallery provides a huge range of PowerShell artifacts for you to leverage. To some degree, if you can conceive of a use for PowerShell, there are likely to be things in the Gallery to delight you. If you are a Grateful Dead fan, for example, there are some scripts to manage your live concert collection (https://www.powershellgallery.com/packages/gdscripts/1.0.4). If you need to generate nice-looking HTML reports, then you can use the PSWriteHTML module (https://www.powershellgallery.com/packages/PSWriteHTML/0.0.173)

PowerShellGet is a module that enables you to discover, install, update, and publish key PowerShell artifacts found in the PowerShell Gallery. The artifacts you can leverage include modules, scripts, and more. You can think of this module as an abstraction on top of the Package Management tools. You can read more about the module at: https://docs.microsoft.com/powershell/module/powershellget.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Reviewing the commands available in the PowerShellGet module

Get-Command -Module PowerShellGet

Discovering Find-* cmdlets in PowerShellGet module

Get-Command -Module PowerShellGet -Verb Find

Getting all commands, modules, DSC resources and scripts

$COM = Find-Command
$MOD = Find-Module
$DSC = Find-DscResource
$SCR = Find-Script

Reporting on results

"On Host [$(hostname)]"
"Commands found:        [{0:N0}]"  -f $COM.count
"Modules found:         [{0:N0}]"  -f $MOD.count
"DSC Resources found:   [{0:N0}]"  -f $DSC.count
"Scripts found:         [{0:N0}]"  -f $SCR.count

Discovering NTFS-related modules

$MOD |
  Where-Object Name -match NTFS

Installing the NTFSSecurity module

Install-Module -Name NTFSSecurity -Force

Reviewing module contents

Get-Command -Module NTFSSecurity

Testing the Get-NTFSAccess cmdlet

Get-NTFSAccess -Path C:\Foo

Creating a download folder

$DLFLDR = 'C:\Foo\DownloadedModules'
$NIHT = @{
  ItemType = 'Directory'
  Path   = $DLFLDR
  ErrorAction = 'SilentlyContinue'
}
New-Item @NIHT | Out-Null

Downloading the PSLogging module

Save-Module -Name PSLogging -Path $DLFLDR

Viewing the contents of the download folder

Get-ChildItem -Path $DLFLDR -Recurse -Depth 2|
  Format-Table -Property FullName

Importing the PSLogging module

$ModuleFolder = Join-Path -Path $DLFLDR -ChildPath 'PSLogging'
Get-ChildItem -Path $ModuleFolder -Filter *.psm1 -Recurse |
  Select-Object -ExpandProperty FullName -First 1 |
    Import-Module -Verbose

Checking commands in the module

Get-Command -Module PSLogging

How it works...

In step 1, you use the Get-Command cmdlet to investigate the commands in the PowerShellGet module with output that should look like this:

Figure 2.24: The PowerShell 7 console

In step 2, you discover the cmdlets in the PowerShellGet module that have the Find verb, with output like this:

Figure 2.25: Discovering the Find-* cmdlets in the PowerShellGet module

In step 3, which generates no output, you find all the commands, modules, DSC resources, and scripts in the PowerShell Gallery. In step 4, you report on the number of objects found in the PS Gallery, with output like this:

Figure 2.26: Discovering the Find-* cmdlets in the PowerShellGet module

In step 5, you examine the modules returned (in step 3) to see if any of them have "NTFS" in the module name. The output looks like this:

Figure 2.27: Discovering NTFS related modules in the PowerShell Gallery

Having discovered an NTFS-related module, in step 6, you install the module. This step creates no output. In step 7, you use Get-Command to discover the commands in the NTFSSecurity module, with output like this:

Figure 2.28: Discovering commands in the NTFSSecurity module

In step 8, you use a command from the NTFSSecurity module, Get-NTFSAccess to view the access control list on the C:\Foo folder, which looks like this:

Figure 2.29: Using Get-NTFSAccess

As an alternative to installing fully a PS Gallery module, you might find it useful to download a module into a new folder. From there, you can investigate (and possibly improve it) before installing it in your environment. To do that, in step 9, you create a new download folder, creating no output. In step 10, you download the PSLogging module which also produces no output.

In step 11, you discover the files contained in the PSLogging module with output like this:

Figure 2.30: Discovering files contained in the PSLogging module

Finally, in step 12, you check the commands contained in this module with output like this:

Figure 2.31: Discovering files contained in the PSLogging module

There's more...

In step 2, you discover the commands within the PowerShellGet module that enable you to find resources in the PS Gallery. There are 5 types of resources supported:

Command – these are individual commands contained within the gallery. You can use Find-Command to help you discover the name of a module that might contain a command.

Module – these are PowerShell modules. Some of which may not work in PowerShell 7 and some that may not work at all.

DSC Resource – these are Windows PowerShell DSC resources. PowerShell 7 does not provide the rich DSC functions and features available with Windows PowerShell at this time.

Script – these are complete PowerShell scripts. You can use themas-iss or adapt them to your needs.

Role Capability – this resource was meant for packages that enhance Windows roles but is little used in practice

In steps 3 and step 4, you discover the number of commands, modules, DSC Resources, and PowerShell scripts available in the gallery. Since there is constant activity, the numbers of PowerShell resources you discover are likely different from what you see in this book

In step 5, you search the PS Gallery for modules whose name includes the string "NTFS". The modules returned may or may be helpful for you. As you can see, the search returns the NTFSSecurity module that, in steps 6 through step 8, you install and use.

Before you start using a PS Gallery module, it is a great idea to first download it and test it. In step 9, you create a new folder, and then in step 10, you download the module into this new folder. In step 11 you observe the contents of the module.

Since you have downloaded the module to a folder not on the PSModulePath environment variable, PowerShell cannot find it automatically. Therefore, in step 12, you import the module manually. You then look at the commands available within the module.

The two modules you examined in this recipe are a tiny part of the PowerShell Gallery. As you discovered, there are thousands of modules, commands, and scripts. Some of those objects are not of the highest quality or may be no use to you. But others are excellent additions to your module collection, as many recipes in this book demonstrate.

For most IT pros, the PowerShell Gallery is the go-to location for obtaining useful modules that avoid you having to re-invent the wheel. In some cases, you may develop a particularly useful module and then publish it to the PS Gallery to share with others. See https://docs.microsoft.com/en-us/powershell/gallery/concepts/publishing-guidelines for guidelines regarding publishing to the PS Gallery. And, while you are looking at that page, consider implementing best practices suggested in any production script you develop.

Creating and using a local package repository

In the "Exploring PowerShellGet and PS Gallery" recipe, you saw how you could download PowerShell modules and more from the PS Gallery. You can install them, or save them for investigation. One nice feature is that after you install a module using Install-Module, you can later update the module using Update-Module.

As an alternative to using a public repository, you can create your own private repository. You can then use the commands in the PowerShellGet module to find, install, and manage your modules. A private repository allows you to create your modules and put them into a local repository for your IT professionals, developers, or other users to access.

There are several methods you can use to set up your internal package repository. One approach would be to use, a third-party tool such as ProGet from Inedo (see https://inedo.com/ for details on ProGet).

A simple way to create a repository is to set up an SMB file share. Then you use the command Register-PSRepository to enable you to use the PowerShellGet commands to view this share as a PowerShell repository. You must register this repository for every host from which you access your repositiory. After you create the share and register the repository, you can publish your modules to the new repository using the Publish-Module command.

Once you set up a repository, you just need to ensure you use Register-PSRepository on any system that wishes to use this new repository, as you can see in this recipe.

Getting ready

Run this recipe on SRV1 after you have loaded PowerShell 7.

How to do it...

Creating a new repository folder

$LPATH = 'C:\RKRepo'
New-Item -Path $LPATH -ItemType Directory | Out-Null

Sharing the folder

$SMBHT = @{
  Name        = 'RKRepo'
  Path        = $LPATH
  Description = 'Reskit Repository'
  FullAccess  = 'Everyone'
}
New-SmbShare @SMBHT

Registering the repository as trusted (on SRV1)

$Path = '\\SRV1\RKRepo'
$REPOHT = @{
  Name               = 'RKRepo'
  SourceLocation     = $Path
  PublishLocation    = $Path
  InstallationPolicy = 'Trusted'
}
Register-PSRepository @REPOHT

Viewing configured repositories

Get-PSRepository

Creating a Hello World module folder

$HWDIR = 'C:\HW'
New-Item -Path $HWDIR -ItemType Directory | Out-Null

Creating a very simple module

$HS = @"
Function Get-HelloWorld {'Hello World'}
Set-Alias GHW Get-HelloWorld
"@
$HS | Out-File $HWDIR\HW.psm1

Testing the module locally

Import-Module -Name $HWDIR\HW.PSM1 -Verbose
GHW

Creating a PowerShell module manifest for the new module

$NMHT = @{
  Path              = "$HWDIR\HW.psd1"
  RootModule        = 'HW.psm1'
  Description       = 'Hello World module'
  Author            = 'DoctorDNS@Gmail.com'
  FunctionsToExport = 'Get-HelloWorld'
  ModuleVersion     = '1.0.1'
}
New-ModuleManifest @NMHT

Publishing the module

Publish-Module -Path $HWDIR -Repository RKRepo -Force

Viewing the results of publishing

Find-Module -Repository RKRepo

Checking the repository's home folder

Get-ChildItem -Path $LPATH

How it works...

In step 1, you create a new folder on SRV1 to hold your new PowerShell repository, producing no output. In step 2, you use the New-SmbShare cmdlet to create a new share, with output like this:

Figure 2.32: The PowerShell 7 console

In step 3, you register this new SMB share as a new PowerShell repository which creates no console output. In step 4, you view the configured PowerShell repositories, with output like this:

Figure 2.33: Viewing configured repositories

To demonstrate using this new repository, in step 5, you first create a folder, and then in step 6 you create a simple module in this folder. These two steps produce no console output.

In step 7, you import and use the module, with output like this:

Figure 2.34: Testing HW module

In step 8, you create a module manifest and in step 9, you publish the module to your repository. These two steps produce no output. In step 10, you use Find-Module to find the modules in your repository, with output like this:

Figure 2.35: Viewing the modules in the new repository

In step 11, you view the repository's home folder, with output like this:

Figure 2.36: Testing HW module

There's more...

In step 1, you create a folder on the C:\ drive to act as a repository which you then share. In production, you should probably put this share in a highly available system with redundant disk drives.

In step 3, you set up an SMB share to act as a PowerShell repository on SRV1. If you plan to use this repository on other systems, you need to explicitly register this repository on each host since registering a repository works on a system-by-system basis.

In step 11, you can see the NuGet package that is your module. When you published the module, the cmdlet created and stored the NuGet package in the shared folder.

Establishing a script signing environment

You can often find that it is essential to know if an application, or a PowerShell script, has been modified since it was released. You can use Windows Authenticode Digital Signatures for this purpose.

Authenticode is a Microsoft code-signing technology that identifies the publisher of Authenticode-signed software. Authenticode also verifies that the software has not been tampered with since it was signed and published.

You can also use Authenticode to digitally sign your script using a PowerShell command. You can then ensure PowerShell only runs digitally signed scripts by setting an execution policy of AllSigned or RemoteSigned.

After you sign a PowerShell script, you can set PowerShell's execution policy to force PowerShell to test the script to ensure the digital signature is still valid and only run scripts that succeed. You can set PowerShell to do this either for all scripts (you set the execution policy to AllSigned) or only for scripts you downloaded from a remote site (by setting the execution policy to RemoteSigned). Setting the execution policy to AllSigned also means that your Profile files must be signed, or they do not run.

This sounds a beautiful thing, but it is worth remembering that even if you have the execution policy set to AllSigned, it's trivial to run any non-signed script. Simply bring your script into VS Code (or the Windows PowerShell ISE) select all the text in the script, then run that selected text. If an Execution policy of RemoteSigned is blocking a particular script you downloaded from the Internet or from the PS Gallery, you can use the Unblock-File cmdlet to, in effect, turn a remote script into a local one. Script signing just makes it a bit harder, but not impossible, to run a script which has no signature or whose signature fails.

Signing a script is simple once you have a digital certificate issued by a Certificate Authority (CA). You have three options for getting an appropriate code-signing certificate:

Use a well-known public Certificate Authority such as Digicert (see https://www.digicert.com/code-signing for details of their code-signing certificates).

Deploy an internal CA and obtain the certificate from your organization's CA.

Use a self-signed certificate.

Public certificates are useful but generally not free. You can easily set up your own CA or use self-signed certificates. Self-signed certificates are great for testing out signing scripts and then using them, but possibly inappropriate for production use. All three of these methods can give you a certificate that you can use to sign PowerShell scripts.

This recipe shows how to sign and use digitally signed scripts. The mechanisms in this recipe work on any of the three sources of signing key listed above. For simplicity, you use a self-signed certificate for this recipe.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Creating a script-signing self-signed certificate

$CHT = @{
  Subject           = 'Reskit Code Signing'
  Type              = 'CodeSigning'
  CertStoreLocation = 'Cert:\CurrentUser\My'
}
New-SelfSignedCertificate @CHT | Out-Null

Displaying the newly created certificate

$Cert = Get-ChildItem -Path Cert:\CurrentUser\my -CodeSigningCert
$Cert |
  Where-Object {$_.SubjectName.Name -match $CHT.Subject}

Creating and viewing a simple script

$Script = @"
  # Sample Script
  'Hello World from PowerShell 7!'
  "Running on [$(Hostname)]"
"@
$Script | Out-File -FilePath C:\Foo\Signed.ps1
Get-ChildItem -Path C:\Foo\Signed.ps1

Signing your new script

$SHT = @{
  Certificate = $cert
  FilePath    = 'C:\Foo\Signed.ps1'
}
Set-AuthenticodeSignature @SHT

Checking script after signing

Get-ChildItem -Path C:\Foo\Signed.ps1

Viewing the signed script

Get-Content -Path C:\Foo\Signed.ps1.

Testing the signature

Get-AuthenticodeSignature -FilePath C:\Foo\Signed.ps1 |
  Format-List

Running the signed script

C:\Foo\Signed.ps1

Setting the execution policy to all signed for this process

Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope Process

Running the signed script

C:\Foo\Signed.ps1

Copying Certificate to Current User Trusted Root store

$DestStoreName  = 'Root'
$DestStoreScope = 'CurrentUser'
$Type = 'System.Security.Cryptography.X509Certificates.X509Store'
$MHT = @{
  TypeName = $Type
  ArgumentList  = ($DestStoreName, $DestStoreScope)
}
$DestStore = New-Object  @MHT
$DestStore.Open(
  [System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$DestStore.Add($Cert)
$DestStore.Close()

Checking the signature

Get-AuthenticodeSignature -FilePath C:\Foo\Signed.ps1 |
  Format-List

Running the signed script

C:\Foo\Signed.ps1

Copying cert to Trusted Publisher store

$DestStoreName  = 'TrustedPublisher'
$DestStoreScope = 'CurrentUser'
$Type = 'System.Security.Cryptography.X509Certificates.X509Store'
$MHT = @{
  TypeName = $Type
  ArgumentList  = ($DestStoreName, $DestStoreScope)
}
$DestStore = New-Object  @MHT
$DestStore.Open(
  [System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$DestStore.Add($Cert)
$DestStore.Close()

Running the signed script

C:\Foo\Signed.ps1

Resetting the Execution Policy for this process

Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process

How it works...

In step 1, you create a new self-signed certificate for code signing. This step creates no output. In step 2, you display the newly created certificate, with output like this:

Figure 2.37: Displaying the certificate

In step 3, you create a very simple script and view the script file's details, with the following output:

Figure 2.38: Displaying the certificate

With step 4, you sign the script using your newly created code-signing certificate. The output is as follows:

Figure 2.39: Signing your script

In step 5, you view the file details for your newly signed script, with output like this:

Figure 2.40: Signing your script

Next, in step 6, you view the contents of the script file, with output like this:

Figure 2.41: Viewing the signed script

In step 7, you use the Get-AuthenticodeSignature to get the details of the file signature and view the results, as you can see here:

Figure 2.42: Viewing details of the script's digital signature

In step 8, you run the script with output like this:

Figure 2.43: Running the script

In step 9, you change the current process's Execution policy to AllSigned, which generates no console output. In step 10 you run the script again with the output like this:

Figure 2.44: Running the script

In step 11, you copy the self-signed certificate to the current user's trusted root CA certificate store. There is no output from the console, but when PowerShell attempts to add the certificate, Windows opens a security warning message box that looks like this:

Figure 2.45: Authorizing the addition of the certificate

In step 12, you re-check the script's digital signature which looks like this:

Figure 2.46: Checking the script's digital signature

In step 13, you re-run the script file. Doing so generates a warning message as you can see here:

Figure 2.47: Re-running the script

To resolve the warning message, in step 14, you copy the certificate to the current user's trusted publisher store. Then in step 15, you run the signed script successfully as you can see in this output:

Figure 2.48: Re-running the script

In the final step in this recipe, step 16, you re-set the process level execution policy to Unrestricted.

There's more...

In step 1, you open a new Windows PowerShell console. Make sure you run the console as the local administrator

In step 4, you sign the script using the newly created code-signing certificate. The output from this step shows that you signed the script with a certificate that was not issued by a trusted certificate authority. In step 5, you can see that the script file exists, but is now 2136 bytes! To see why this relatively large file size, in step 6, you can see the script, with a large text block at the end holding the digital signature.

With step 7, you verify the signature on the script, which results in the same status message. The file is signed, but Windows does not (yet) trust the Certificate Authority that issued the code-signing certificate.

In step 8, you run the script. Despite the signature issue, the script runs fine, since you set the PowerShell execution policy to Unrestricted when you installed PowerShell. In step 9, you set the execution policy to Allsigned and in step 10, you run the script. As you can see, PowerShell stops you from running the script due to the certificate chain issue.

In step 11, you copy the self-signed certificate into the current user's trusted root certificate store. You can only perform this copy action by using .NET objects as there are no cmdlets. When you invoke the Add() method, Windows displays a security warning. Note that Windows tends top display this dialog box under your PowerShell console or VS code. When you run this step, if the code seems to hang, check to see if the dialog box is hidden before agreeing to the addition to complete the step.

By copying the self-signed certificate to the trusted root store, Windows believes the code signing certificate is signed by a trusted CA. In step 12, you verify that the signature is trusted. Despite this, in step 13, you we a warning when you attempt to run the signed script. To avoid this warning, you copy the certificate to the current user's trusted publisher certificate store. With this done, in step 15, you re-run the script to observe that it now runs successfully.

In a production environment, you would more likely use either a public CA or an internal CA. If you use a public CA, then Windows should have that CA's certificate in the local host's trusted root store. You may need to add that certificate to the Trusted Publisher's store on each local host that you want to be able to run signed scripts. If you use an internal CA, you must ensure that each host is automatically enrolled for these certificates.

Working with shortcuts and the PSShortCut module

A shortcut is a file which contains a pointer to another file or URL. You can place a shell link shortcut to some executable program, such as PowerShell, on your Windows desktop. When you click the shortcut in Windows Explorer, Windows runs the target program. You can also create a shortcut to a URL.

Shell link shortcuts have the extension .LNK, while URL shortcuts have the .URL extension. Internally, a file shortcut has a binary structure which is not directly editable. For more details on the internal format, see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/.

The URL shortcut is a text document which you could edit with VS Code or Notepad. For more details on the URL shortcut file format, see http://www.lyberty.com/encyc/articles/tech/dot_url_format_-_an_unofficial_guide.html.

There are no built-in commands to manage shortcuts in PowerShell 7. As you saw earlier in this book, you can make use of older COM objects to create shortcuts. A more straightforward way is to use the PSShortcut module, which you can download from the PowerShell Gallery.

In this recipe, you discover shortcuts on your system and create shortcuts both to an executable file and a URL.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Finding the PSShortcut module

Find-Module -Name '*Shortcut'

Installing PSShortcut module

Install-Module -Name PSShortcut -Force

Reviewing PSShortcut module

Get-Module -Name PSShortCut -ListAvailable |
  Format-List

Discovering commands in PSShortcut module

Get-Command -Module PSShortcut

Discovering all shortcuts on SRV1

$SHORTCUTS = Get-Shortcut
"Shortcuts found on $(hostname): [{0}]" -f $SHORTCUTS.Count

Discovering PWSH shortcuts

$SHORTCUTS | Where-Object Name -match '^PWSH'

Discovering URL shortcut

$URLSC = Get-Shortcut -FilePath *.url
$URLSC

Viewing content of shortcut

$URLSC | Get-Content

Creating a URL shortcut

$NEWURLSC  = 'C:\Foo\Google.url'
$TARGETURL = 'https://google.com'
New-Item -Path $NEWURLSC | Out-Null
Set-Shortcut -FilePath $NEWURLSC -TargetPath $TARGETURL

Using the URL Shortcut

& $NEWURLSC

Creating a file shortcut

$CMD  = Get-Command -Name notepad.exe
$NP   = $CMD.Source
$NPSC = 'C:\Foo\NotePad.lnk'
New-Item -Path $NPSC | Out-Null
Set-Shortcut -FilePath $NPSC -TargetPath $NP

Using the shortcut

& $NPSC

How it works...

In step 1, you use the Find-Module cmdlet to discover any modules with the string "Shortcut" in tgeh module name. The output looks like this:

Figure 2.49: Re-running the script

In step 2, you install the PSShortcut module, producing no output. After you have the module installed, in step 3, you examine the module details, with output like this:

Figure 2.50: Viewing module details

In step 4, you use Get-Command to confirm the names of the commands in the module, with output like this:

Figure 2.51: Using Get-Command to find the commands in the PSShortcut module

As shown in step 5, you can search the entire host to discover all the shortcuts on the server, with results like this:

Figure 2.52: Finding shortcuts on SRV1

In step 6, you discover the shortcut to PowerShell 7 (which you created in Chapter 1). The output is like this:

Figure 2.53: Discovering shortcuts to PowerShell 7

In step 7, you use the Get-Shortcut command to find all the URL shortcuts, with output like this:

Figure 2.54: Discovering URL shortcuts

In step 8, you view the contents of the shortcut, with output like this:

Figure 2.55: Viewing the contents of a URL shortcut

In step 9, you create a new URL shortcut, which generates no console output. In step 10, you use the URL shortcut. This step produces no condole output, but you do see the Edge browser popping up, like this:

Figure 2.56: Using a shortcut to Google

In step 11, you create a new file-based URL (pointing to notepad.exe, which generates no output. Then in step 12, you use the shortcut. This step produces no output, but you should see Notepad pop up, like this:

Figure 2.57: Using the shortcut to Notepad

There's more...

In step 7, you find URL shortcuts and as you can see, there is just one, to Bing. The Windows installer created this when it installed Windows Server on this host.

In step 8, you examine the contents of a URL shortcut. Unlike link shortcut files, which have a binary format and are not fully readable, URL shortcuts are text files. For an unofficial guide to the format of these files see http://www.lyberty.com/encyc/articles/tech/dot_url_format_-_an_unofficial_guide.html.

Working with Archive files

Since the beginning of the PC era, users have employed a variety of file compression mechanisms. An early method used the ZIP file format, initially implemented by PKWare's PKZip program quickly became a near-standard for data transfer. A later Windows version, WinZip, became popular. With Windows 98, Microsoft provided built-in support for .ZIP archive files. Today, Windows supports ZIP files up to 2GB in total size. You can file more information about the ZIP file format at https://en.wikipedia.org/wiki/Zip_(file_format).

Numerous developers have, over the years, have provided alternative compression schemes and associated utilities, including WinRAR and 7-ZIP. WinZip and WinRAR are both excellent programs, but are commercial programs. 7-Zip is a freeware tool that is also popular. All three offer their own compression mechanisms (with associated file extension) and support the others as well.

For details on WinZip see https://www.winzip.com/win/en, for information on WinRAR see https://www.win-rar.com, and for more on 7Zip see https://www.7-zip.org. Each of the compression utilities offered by these groups also supports compression mechanisms from other environments such as TAR.

In this recipe, you look at PowerShell 7's built-in commands to manage archive files. The commands work only with .ZIP files. You can find a PowerShell module for 7Zip at https://github.com/thoemmi/7Zip4Powershell although the module is.is old and has not been updated in many years.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Getting archive module

Get-Module -Name Microsoft.Powershell.Archive -ListAvailable

Discovering commands in archive module

Get-Command -Module Microsoft.PowerShell.Archive

Making a new folder

$NIHT = @{
  Name        = 'Archive'
  Path        = 'C:\Foo'
  ItemType    = 'Directory'
  ErrorAction = 'SilentlyContinue'
}
New-Item @NIHT | Out-Null

Creating files in the archive folder

$Contents = "Have a Nice day with PowerShell and WIndows Server" * 1000
1..100 |
  ForEach-Object {
    $FName = "C:\Foo\Archive\Archive_$_.txt"
    New-Item -Path $FName -ItemType File  | Out-Null
    $Contents | Out-File -FilePath $FName
}

Measuring files to archive

$Files = Get-ChildItem -Path 'C:\Foo\Archive'
$Count = $Files.Count
$LenKB = (($Files | Measure-Object -Property length -Sum).Sum)/1mb
"[{0}] files, occupying {1:n2}mb" -f $Count, $LenKB

Compressing a set of files into an archive

$AFILE1 = 'C:\Foo\Archive1.zip'
Compress-Archive -Path $Files -DestinationPath "$AFile1"

Compressing a folder containing files

$AFILE2 = 'C:\Foo\Archive2.zip'
Compress-Archive -Path "C:\Foo\Archive" -DestinationPath $AFile2

Viewing the archive files

Get-ChildItem -Path $AFILE1, $AFILE2

Viewing archive content with Windows Explorer

explorer.exe $AFILE1

Viewing second archive with Windows Explorer

explorer.exe $AFILE2

Making new output folder

$Opath = 'C:\Foo\Decompressed'
$NIHT2 = @{
  Path        = $Opath
  ItemType    = 'Directory'
  ErrorAction = 'SilentlyContinue'
}
New-Item @NIHT2 | Out-Null

Decompress the Archive1.zip archive

Expand-Archive -Path $AFILE1 -DestinationPath $Opath

Measuring the size of the decompressed files

$Files = Get-ChildItem -Path $Opath
$Count = $Files.Count
$LenKB = (($Files |
     Measure-Object -Property length -Sum).Sum)/1mb
"[{0}] decompressed files, occupying {1:n2}mb" -f $Count, $LenKB

How it works...

In step 1, you get details of the Microsoft.Powershell.Archive module, with output like this:

Figure 2.58: Using the shortcut to Notepad

In step 2, you use Get-Command to discover additional details about the commands contained in the Archive module, with output like this;

Figure 2.59: Viewing the commands in the Microsoft.PowerShell.Archive module

To prepare to use the archiving commands, in step 3, you create a new folder and then in step 4, you populate the folder with a set of files. These two steps produce no output. In step 5, you get a count of the files in the folder and the space they occupy, with output like this:

Figure 2.60: Measuring the files to be compressed

In step 6, you compress a set of files into an archive file, and in step 7, you compress a folder (containing files) into another archive file. These two steps produce no output. In step 8, you view the two archive files, with output like this:

Figure 2.61: Viewing the archive files

In step 9, you view the first archive file in Windows File Explorer, with output like this:

Figure 2.62: Viewing Archive1.zip

In step 10, you view the second archive file which looks like this:

Figure 2.63: Viewing Archive1.zip

To demonstrate expanding an archive, in step 11, you create a new folder (C:\Foo\Decompressed) which generates no console output, In step 12, you use Expand-Archive to de-compress the files within the archive to the specified destination folder which also generates no output.

In the final step, step 13, you count the decompressed files with output like this:

Figure 2.64: Viewing Archive1.zip

There's more...

In step 1 and in step 2, you can see the Archive module has two commands internally. As you can see, the module details retuned by Get-Module show the module to have two commands. In the next step, Get-Command confirms the module has the two commands but that these are PowerShell functions and not PowerShell cmdlets. If you navigate to the folder containing this module you can see both the module manifest (Microsoft.PowerShell.Archive.psd1) and the module file that defines the exported functions (Microsoft.PowerShell.Archive.psm1).

In step 9, you created the archive file Archive1.zip that contains 100 files. In step 12, you used Expand-Archive to expand the files from the archive into the specified folder. As you can see in step 13, the files previously compressed into an archive are now decompressed and in the destination path.

Searching for files using the Everything search tool

Void Tools is a software company that produced the Everything search tool. Most IT Pros run it from the GUI but there is a community module to help you use this tool using PowerShell. The Everything tool and the PowerShell community have developed a nice little product that you can easily leverage in PowerShell. The Everything applications returns fully qualified file names for those files on your system whose names meet a specified pattern. You can use either wild card (e.g. *.PS1), or a regular expression (e.g. '\.PS1$') as a pattern. an There is also an add-on module that provides and easy to use PowerShell COMMAND.,

The key benefit is that it is very fast. On a large Windows host with say one million total files, it might take 10-12 seconds to return a list of all the files on that system). To find all the .PS1 files on the system drive would take under 100 milliseconds. By comparison, using Get-ChildItem would take several minutes.

If you are managing a large file server, tools like Everything could be very useful. And for a bit of light relief, you can bring up the GUI, specify ALL files, and sort files by date modified. After a second or two, you can watch as Windows and Windows applications create and update files on your system. Do you know what all those files are?

This recipe first downloads and installs the Everything tool and the PSEverything module from the PowerShell Gallery. Then you use the tool to find files on the system.

For more details on the Everything tool, see: https://www.voidtools.com/. For the latest downloads, you can view the download page at: https://www.voidtools.com/downloads/. And for more information about installing Everything, see a great support article at: https://www.voidtools.com/forum/viewtopic.php?f=5&t=5673&p=15546. The tool provides a large range of installation options that you can use, depending on your needs.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Getting download locations

$ELoc  = 'https://www.voidtools.com/downloads'
$Release = Invoke-WebRequest -Uri $ELoc # Get all
$FLoc  = 'https://www.voidtools.com'
$EPath = $FLOC + ($Release.Links.href |
     Where-Object { $_ -match 'x64' } |
     Select-Object -First 1)
$EFile = 'C:\Foo\EverythingSetup.exe'

Downloading the Everything installer

Invoke-WebRequest -Uri $EPath -OutFile $EFile -verbose

Install Everything

$Iopt = "-install-desktop-shortcut -install-service"
$Iloc = 'C:\Program Files\Everything'
.\EverythingSetup.exe /S -install-options  $Iipt /D=$Iopt

Open the GUI for the first time

& "C:\Program Files\Everything\Everything.exe"

Finding the PSEverything module

Find-Module -Name PSEverything

Installing the PSEverything module

Install-Module -Name PSEverything -Force

Discovering commands in the module

Get-Command -Module PSEverything

Getting a count of files in folders below C:\Foo

Search-Everything |
  Get-Item |
    Group-Object DirectoryName |
      Where-Object name -ne '' |
        Format-Table -property Name, Count

Finding PowerShell scripts using wild cards

Search-Everything *.ps1 |
  Measure-Object

Finding all PowerShell scripts using regular expression

Search-Everything -RegularExpression '\.ps1$' -Global |
  Measure-Object

How it works...

In step 1, you set the locations for the download of the Everything tool. Then in step 2, you download the installation package. With step 3, you perform a silent install of the tool onto SRV1. With step 4, you open the Everything GUI. This produces no console output but you do see the following popup:

Figure 2.65: Everything GUI

With step 5, you find the PSEverything module with output like this:

Figure 2.66: Finding the PSEverything module

In step 6, you install the module which generates no console output. In step 7 you view the commands in the module with output like this:

Figure 2.67: Getting the commands in the PSEverything module

Figure 2.68: Counting the files in each folder below C:\Foo on SRV1

In step 8, you search for all the *.PS1 files at and below C:\Foo then return a count, with output like this:

Figure 2.69: Counting the *.PS1 files in and below C:\Foo

In the final step in this recipe, step 10, you count the total number of files on SRV1 whose filename matches the regular expression "\,ps1$" – which is the equivalent of the wild card used in the prior step. The output from this step looks like this:

Figure 2.70: Counting the *.PS1 files on SRV1

There's more...

In step 1, you set variables that correspond to the location from which you can download the Everything installation package. This location was correct at the time of writing – but may have changed.

The screenshot for step 4 shows the Everything GUI with the files sorted with the most recently changed files at the top of the window. Note the number of files that Windows changes every minute! Do you know what these files are or why Windows is changing them? Answers on a postcard, please.

In step 8, you view the folders below C:\Foo and the files contained in each folder. You created these folders and files by performing the recipes in this book. If you skipped any of the prior recipes, loaded different modules, etc. you may see different output.

The Search-Everything cmdlet returns the file names that match a specified pattern. The cmdlet does not search inside any of the files. As you see in step 7, there is another command in the module, Select-EverythingString that does search inside the files found.

3 Exploring .NET

Feedback

We are constantly looking at improving our content, so what could be better than listening to what you as a reader have to say? Your feedback is important to us and we will do our best to incorporate it. Could you take two mins to fill out the feedback form for this book and let us know what your thoughts are about it? Here's the link: https://packt.link/A8yX4.

Thank you in advance.

This chapter covers the following recipes:

Exploring .NET Assemblies

Exploring .NET Classes

Leveraging .NET Methods

Creating a C# Extension

Creating a cmdlet

Introduction

Microsoft first launched the Microsoft .NET Framework in June 2000, with the code name Next Generation Windows Services. Amidst a barrage of marketing zeal, Microsoft seemed to add the .NET moniker to every product in its portfolio: Windows .NET Server (later renamed Windows Server 2003), Visual Studio.NET, and even MapPoint .NET.

The .NET Framework provided application developers with a host of underlying features and technologies on which to base their applications. These worked well then (20+ years ago), but newer features later emerged based on advances in the underlying technologies. For example, SOAP (Simple Object Access Protocol) and XML-based web services have given way to REST (Representation State Transfer) and JSON (JavaScript Object Notation).

Microsoft made considerable improvements to the .NET Framework with each release and added new features based on customer feedback. The .NET started as closed-source, but Microsoft transitioned .NET to open source, aka .NET Core. PowerShell 7.2 is based on .Net 6.0.

An issue, over time, was that .NET has become fragmented across different OSs and the web. To resolve this problem, Microsoft created Net 5.0 and dropped “core” moniker). They released this version both on the web and across hardware platforms such as ARM. The intention, from now on, is to move to a single .NET across all platforms and form factors, thus simplifying application development. See https://www.zdnet.com/article/microsoft-takes-a-major-step-toward-net-unification-with-net-5-0-release/ for some more detail on this.

It is important to note that both .NET 6.0 and PowerShell 7.2 have long-term support (LTS). Microsoft intends to support these two products for three years, as described in this article: https://devblogs.microsoft.com/dotnet/announcing-net-6/. Many larger organizations prefer products with a longer support horizon.

For the application developer, .NET provides a rich Application Program Interface (API), a programming model, plus associated tools and run-time. .NET is an environment in which developers can develop rich applications and websites across multiple platforms.

PowerShell sits on top of and makes heavy use of .NET. Some cmdlets are just a thin wrapper around .NET Objects. For the IT Professional, .NET provides the underpinnings of PowerShell but is otherwise mostly transparent. You use cmdlets to carry out actions. While these cmdlets use .NET to carry out their work, you generally are unaware of how PowerShell works under the covers. For example, you can use Get-Process without worrying about how that cmdlet obtains details of the processes.

In some cases, there is no cmdlet available to you to carry out some action, but you can use .NET classes and methods to achieve it. For example, if you are creating an Active Directory Cross forest trust. So in some cases, knowing how to use .NET classes and their associated methods can be useful.

In some cases, you may wish to extend what is available by creating a .NET Class (e.g., using C#) or developing a full PowerShell cmdlet. It is quite easy to download the tools and build a cmdlet. You might have some calculation of a bit of C# code that performs a particular task. Rather than taking the time to convert it to PowerShell and possibly taking a performance hit, you can wrap the code easily and create a class. Then you can add this to the PowerShell environment and use the class directly from PowerShell. And, if you are developing line of business applications, you could also create a full C# cmdlet.

Here is a high-level illustration of the components of .NET.

Figure 3.1: .NET Components

The key components of .NET in this diagram are:

Operating System - the .NET story begins with the operating system. The operating system provides the fundamental operations of your computer. .NET leverages the OS components. The .NET team supports .NET 6.0, the basis for PowerShell 7.2, on Windows, Linux, and the Apple Mac .NET also runs on the ARM platform, enabling you to get PowerShell on ARM. See https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md for a full list of the operating systems supported.

Common Language Runtime (CLR) - the CLR is the core of .NET, the managed code environment in which all .NET applications run (including PowerShell). The CLR delivers a managed execution environment and type safety and code access security. The CLR manages memory, threads, objects, resource allocation/de-allocation, and garbage collection. For an overview of the CLR, see https://docs.microsoft.com/dotnet/standard/clr/. The CLR also contains the Just-In-Time (JIT) compiler and the class loader (responsible for loading classes into an application at run time). For the PowerShell user, the CLR ‘‘just works’’.

Base Class Libraries (BCLs) - .NET comes with numerous base class libraries, the fundamental built-in features you can use to build .NET applications. Each BCL is a DLL that contains .NET classes and types. The installer adds the.NET Framework components into PowerShell’s installation folder when you install PowerShell. PowerShell developers use these libraries to implement PowerShell cmdlets. You can also call classes in the BCL directly from within PowerShell. It is the BCLs that enable you to reach into .NET

WMI, COM, Win32 - traditional application development technologies you can access via PowerShell. Within PowerShell, you can run programs that use these technologies and access them from the console or via a script. Your scripts can be a mixture of cmdlets, .NET, WMI, COM, and Win32 if necessary.

Languages - this is the language that a cmdlet and application developer uses to develop PowerShell cmdlets. You can use various languages based on your preferences and skill set. Most cmdlet developers use C#, although using any .NET supported language would work, such as VB.Net. The original PowerShell development team designed the PowerShell language based on C#. You can describe PowerShell as on the glide scope down to C#. Understanding C# can be useful since there is a lot of documentation with examples in C#.

PowerShell - PowerShell sits on top of these other components. From PowerShell, you can use cmdlets developed using a supported language. And you can use both BCL and WMI/COM/Win32.

Before diving into using .NET, there are some terms you should understand

Class – a class is a definition of some kind of object: the System.Int32 defines a signed 32-bit integer while the System.Diagnostics.Process class defines a Windows process. Developers typically use C#, but you have options.

Assembly – an assembly is a collection of types and resources used by an application and contains rich meta-data about what the assembly includes. Most assemblies you use are .DLL files that a developer compiled for you previously – you add them using the Add-Type command. A neat feature is that you can also use Add-Type and supply just the source code - the cmdlet does the compilation for you at runtime!

Class instances – you can have multiple instances of a given class, such as the processes on a system. A class instance can have properties, methods, events, and more. A Property is some attribute of a class instance. For example, the StartTime property on a process object holds the process's start time. A method is some action that .NET can take on a given instance. A process instance, for example, has a Kill() method which immediately terminates the process (and does NOT ask you if you are sure!).

Static properties and methods Many classes have static properties and methods. Static properties relate to the class as opposed to a class instance. The System.Int32 class has a static property, MaxValue, that holds a value of the largest value that you can carry in a 32-bit integer instance. The System.IO.FileInfo class has a static method new() that enables you to create a new file.

In the chapter, you examine .NET assemblies, classes, and methods.

The system used in the chapter

This chapter is about using PowerShell 7 to explore .NET. In this chapter, you use a single host, SRV1, as follows:

Figure 3.2: Host in use for this chapter

In later chapters, you will use additional servers and will promote SRV1 to be a domain-based server rather than being in a workgroup.

Exploring .NET Assemblies

With .NET, an assembly holds compiled code that .NET can run. An assembly can be either a Dynamic Link Library (DLL) or an executable. As you can see in this recipe, Cmdlets and .NET classes are generally contained in DLLs. Each assembly also includes a manifest that describes what is in the assembly and compiled code.

Most PowerShell modules and most PowerShell commands use assemblies of compiled code. When PowerShell loads any module, the module manifest (the .PSD1 file) lists the assemblies which make up the module. For example, the Microsoft.PowerShell.Management module provides many core PowerShell commands such as Get-ChildItem and Get-Process. This module’s manifest lists a nested module (i.e., Microsoft.PowerShell.Commands.Management.dll) as the assembly containing the actual commands. PowerShell automatically loads this DLL file for you whenever it loads the module.

A great feature of PowerShell is the ability for you to invoke a .NET Class method directly or to obtain a static .NET Class value (or even execute a static method). The syntax for calling a .NET Method or a .NET field, demonstrated in numerous recipes in this book, is to enclose the class name in square brackets and then follow it with two “:” characters ( that is: “::”) followed by the name of the method or static field (such as [System.Int32]::MaxValue).

In this recipe, you examine the assemblies loaded into PowerShell 7 and compare that with the behavior in Windows PowerShell. You also look at a module and the assembly that implements the commands in the module. The recipe illustrates some of the differences between PowerShell 7 and Windows PowerShell and how they co-exist with .NET.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Counting loaded assemblies

$Assemblies = [System.AppDomain]::CurrentDomain.GetAssemblies()
"Assemblies loaded: {0:n0}" -f $Assemblies.Count

Viewing first 10

$Assemblies | Select-Object -First 10

Checking assemblies in Windows PowerShell

$SB = {
  [System.AppDomain]::CurrentDomain.GetAssemblies()
}
$PS51 = New-PSSession -UseWindowsPowerShell
$Assin51 = Invoke-Command -Session $PS51 -ScriptBlock $SB
"Assemblies loaded in Windows PowerShell: {0:n0}" -f $Assin51.Count

Viewing Microsoft.PowerShell assemblies

$Assin51 |
  Where-Object FullName -Match "Microsoft\.Powershell" |
    Sort-Object -Property Location

Exploring the Microsoft.PowerShell.Management module

$Mod =
  Get-Module -Name Microsoft.PowerShell.Management -ListAvailable
$Mod  | Format-List

Viewing module manifest

$Manifest = Get-Content -Path $Mod.Path
$Manifest | Select-Object -First 20

Discovering the module's assembly

Import-Module -Name Microsoft.PowerShell.Management
$Match = $Manifest | Select-String Modules
$LINE = $Match.Line
$DLL = ($Line -Split '"')[1]
Get-Item -Path $PSHOME\$DLL

Viewing associated loaded assembly

$Assemblies2 = [System.AppDomain]::CurrentDomain.GetAssemblies()
$Assemblies2 | Where-Object Location -match $DLL

Getting details of a PowerShell command inside a module DLL

$Commands  = $Assemblies |
               Where-Object Location -match Commands.Management\.dll
$Commands.GetTypes() |
  Where-Object Name -match "Addcontentcommand$"

How it works...

In step 1, you use the GetAssemblies() static method to return all the assemblies currently loaded by PowerShell. Then you output a count of the assemblies presently loaded, which looks like this:

Figure 3.3: Counting loaded assemblies

In step 2, you look at the first ten loaded assemblies, with output like this:

Figure 3.4: Viewing the first ten loaded assemblies

In step 3, you examine the assemblies loaded into Windows PowerShell 5.1, which looks like this:

Figure 3.5: Counting assemblies loaded in Windows PowerShell

In step 4, you examine the Microsoft.PowerShell.* assemblies in PowerShell 5.1, which looks like this:

Figure 3.6: Examining the Microsoft.PowerShell.* assemblies in Windows PowerShell

The Microsoft.PowerShell.Management PowerShell module contains numerous core PowerShell commands. In step 6, you examine the details of this module, as returned by Get-Module, with output like this:

Figure 3.7: Examining the Microsoft.PowerShell.Management module

In step 6, you view the module manifest for the Microsoft.PowerShell.Management PowerShell module. The figure below shows the first 20 lines of the manifest, which look like this:

Figure 3.8: Examining the module manifest for the Microsoft.PowerShell.Management module

In step 7, you import the Microsoft.PowerShell.Management module, extract the name of the DLL implementing the module and discover its supporting DLL, with output like this:

Figure 3.9: Examining a module’s supporting DLL

In step 8, you find the assembly that contains the cmdlets in the Microsoft.PowerShell.Management module, which looks like this:

Figure 3.10: Examining a module’s supporting DLL

In step 9, you discover the name of the class that implements the Add-Content command, which looks like this:

Figure 3.11: Examining a module’s supporting DLL

There's more...

In this recipe, you have seen the .NET assemblies used by PowerShell. These consist of both the .NET Framework assemblies (i.e., the Base Class Libraries) and the assemblies which implement PowerShell commands. For example, you find the Add-Content cmdlet in the PowerShell module Microsoft.PowerShell.Management.

In step 1, you use the GetAssemblies() static method to return all the assemblies currently loaded by PowerShell 7.2. As you can see, the syntax is different from calling PowerShell cmdlets.

In steps 3 and step 4, you obtain and view the assemblies loaded by Windows PowerShell 5.1. As you can see, Windows PowerShell and PowerShell 7.2 load different assemblies.

In step 6, you view the first 20 lines of the module manifest for the Microsoft.PowerShell.Management module. The output cuts off the complete list of cmdlets exported by the module and the long digital signature for the module manifest. In the output, you can see that the Microsoft.PowerShell.Management.dll contains the Add-Content command.

In step 7, you discover the DLL, which implements, among others, the Add-Content cmdlet, and in step 8, you can see that the assembly is loaded. In step 9, you can discover that the Add-Content PowerShell cmdlet is implemented by the AddContentCommand class within the Assembly’s DLL. For the curious, navigate to https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs, where you can read the source code for this cmdlet.

Exploring .NET Classes

With .NET, a class defines an object in terms of properties, methods, etc. Objects and object occurrences are fundamental to PowerShell, where cmdlets produce and consume objects. The Get-Process command returns objects of the class System.Diagnostics.Process. When you use Get-ChildItem to return files and folders, the output is a set of objects based on the class System.IO.FileInfo and System.IO.DirectoryInfo.

In most cases, your console activities and scripts use the objects created automatically by PowerShell commands. But you can also use the New-Object command to create occurrences of any class as necessary. This book shows numerous examples of creating an object using New-Object.

Within .NET, you have two kinds of object definitions: .NET classes and .NET types. A type defines a simple object that lives, at runtime, on your CPU’s stack. Classes, being more complex, live in the global heap. The global heap is a large area of memory that .NET uses to hold object occurrences during a PowerShell session. In almost all cases, the difference between type and class is probably not overly relevant to IT Professionals using PowerShell.

After a script or even a part of a script has run, .NET can tidy up the global heap in a process known as garbage collection. The garbage collection process is also not important for IT professionals (in most cases). The scripts you see in this book, for example, are not generally impacted by the garbage collection process, nor are most production scripts. For more information on the garbage collection process in .NET, see https://docs.microsoft.com/dotnet/standard/garbage-collection/.

There are cases where the GC process can impact performance. For example, the System.Array class creates objects of fixed length within the global heap. If you add an item to an array, .NET creates a new copy of the array (including the addition) and marks the old one for removal. If you add a few items to the array, the performance hit is negligible. But if you are adding millions of occurrences, the performance can suffer significantly. You can use the ArrayList class to avoid this, which supports adding/removing items from an array without the performance penalty. For more information on GC and performance, see https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/performance.

In .NET, occurrences of every class or type can include members, including Properties, Methods, and Events. A property is an attribute of an occurrence of a class. An occurrence of the System.IO.FileInfo object, for example, has a FullName property. A method is effectively a function you can call which can do something to an object occurrence. You look at .NET Methods in “Leveraging .NET Methods.” With .NET, an event can happen to an object occurrence, such as when Windows generates an event when a Windows process terminates. This book does not cover .NET events; however, a later chapter in this book discusses using WMI events.

You can quickly determine an object’s class (or type) by piping the output of any cmdlet, or an object, to the Get-Member cmdlet. The Get-Member cmdlet uses a feature of .NET, reflection, to look inside and give you a definitive statement of what that object contains. This feature is invaluable - instead of guessing where in some piece of string output your script can find the full name of a file, you can discover the FullName property, a string, or the Length property, which is unambiguously an integer. Reflection and the Get-Member cmdlet help you to discover the properties and other members of a given object.

As mentioned earlier, .NET classes can have static properties and static methods. Static properties/methods are aspects of the class of a whole as opposed to a specific class instance. A static property is a fixed constant value, such as the maximum and minimum values for a 32-bit signed integer or the value of Pi. A static method is independent of any specific instance. For example, the Parse() method of the INT32 class can parse a string to ensure it is a value 32-bit signed integer. In most cases, you use static methods to create object instances or do something related to the class, such as parsing a string to determine if it is a valid 32-bit integer.

This recipe looks at some everyday objects created automatically by PowerShell. The recipe also examines the static fields of the [Int] .NET class.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Creating a Fileinfo object

$FILE = Get-ChildItem -Path $PSHOME\pwsh.exe
$FILE

Discovering the underlying class

$TYPE = $FILE.GetType().FullName
".NET Class name: $TYPE"

Getting member types of Fileinfo object

$File |
  Get-Member |
    Group-Object -Property MemberType |
      Sort-Object -Property Count -Descending

Discovering properties of a Windows service

Get-Service |
  Get-Member -MemberType Property

Discovering the underlying type of an integer

$I = 42
$IntType  = $I.GetType()
$TypeName = $IntType.FullName
$BaseType = $IntType.BaseType.Name
".NET Class name      : $TypeName"
".NET Class base type : $BaseType"

Looking at Process objects

$PWSH = Get-Process -Name pwsh |
  Select-Object -First 1
$PWSH |
  Get-Member |
    Group-Object -Property MemberType |
      Sort-Object -Property Count -Descending

Looking at static properties within a class

$Max = [Int32]::MaxValue
$Min = [Int32]::MinValue
"Minimum value [$Min]"
"Maximum value [$Max]"

How it works...

In step 1, you use the Get-ChildItem cmdlet to return a Fileinfo object. When you examine the object created by the cmdlet, it looks like this:

Figure 3.12: Creating a FileINfo object

In step 2, you use the Get-Type() method to return the full class name of the $File object, which looks like this:

Figure 3.13: Viewing the underlying class name

In step 3, you get the different types of members in an instance of a FileInfo class. You can see the other member types in the output like this:

Figure 3.14: Viewing the member types of a FileInfo object

In step 4, you examine the properties of an object representing a Windows Service, looking like this:

Figure 3.15: Viewing the properties of a Windows service object

In step 5, you examine the underlying type of a 32-bit integer, with output like this:

Figure 3.16: Viewing the class name for a 32-bit integer

In step 6, you look at the different member types within an object representing a Windows process, with the following output:

Figure 3.17: Viewing the member types of a process object

In the final step in this recipe, step 7, you examine two static properties of an Int32 object, with output like this:

Figure 3.18: Viewing the static properties of a 32-bit integer

There's more...

In step 1, you create a FileInfo object representing pwsh.exe. This object’s full type name is System.IO.FileInfo. In .NET, classes live in namespaces and, in general, namespaces equate to DLLs. In this case, the object is in the System.IO namespace, and the class is contained in the System.IO.FileSystem.DLL.

As you use your favorite search engine to learn more about .NET classes that might be useful, note that many sites describe the class without the namespace, simply as the FileInfo class, while others spell out the class as System.IO.FileInfo. If you are using .NET class names in your scripts, a best practice is to spell out the full class name. You can discover namespace and DLL details by examining the class’s documentation – in this case, https://docs.microsoft.com/dotnet/api/system.io.fileinfo.

In step 6, you examine the different member types you can find on a class or class instance for a process object. Some of the members you can see come directly from the underlying .NET object. Others, such as ScriptProperty, AliasProperty, and CodeProperty, are added by PowerShell’s Extensible Type System (ETS). With the ETS, the developers extend the .Net object with, in effect, additional properties which IT Pros may find more useful. You can read about the ETS here: https://powershellstation.com/2009/09/15/powershell-ets-extended-type-system.

Exploring .NET Methods

With .NET, a method is some action that a .NET object occurrence (an instance method), or the class (a static method), can perform. These methods form the basis for many PowerShell cmdlets. For example, you can stop a Windows Process by using the Stop-Process cmdlet. The cmdlet then uses the Kill() method of the associated Process object. As a general best practice, you should use cmdlets wherever possible. You should only use .NET classes and methods directly where there is no alternative.

.NET methods can be beneficial for performing some operations which have no PowerShell cmdlet support. And it can be useful too from the command line, for example, when you wish to kill a process. IT Professionals are all too familiar with processes that are not responding and need to be killed, something you can do at the GUI using Task Manager. Or, with PowerShell, you can use the Stop-Process cmdlet. At the command line, where brevity is useful, you can use Get-Process to find the process you want to stop and pipe the output to each process’s Kill() method. PowerShell then calls the object’s Kill() method. To help IT Professionals, the PowerShell team created the Kill alias to the Stop-Process cmdlet. I

Another great example is encrypting files. Windows’s NTFS file system includes support for the Encrypting File System (EFS) feature. EFS lets you encrypt or decrypt files on your computer with encryption based on X.509 certificates. For details on the EFS and how it works, see https://docs.microsoft.com/ windows/win32/fileio/file-encryption.

At present, there are no cmdlets to encrypt or decrypt files. The System.IO.FileInfo class, however, has two methods you can use: Encrypt() and Decrypt(). These instance methods encrypt and decrypt a file based on EFS certificates (which Windows can automatically generate). You can use these .NET methods to encrypt or decrypt a file without using the GUI.

As you saw in “Examining .NET Classes,” you can pipe any object to the Get-Member cmdlet to discover the methods for the object. Discovering the specific property names and property value types is simple and easy – no guessing or prayer-based text parsing, so beloved by Linux admins.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Starting Notepad

notepad.exe

Obtaining methods on the Notepad process

$Notepad = Get-Process -Name Notepad
$Notepad | Get-Member -MemberType Method

Using the Kill() method

$Notepad |
  ForEach-Object {$_.Kill()}

Confirming Notepad process is destroyed

Get-Process -Name Notepad

Creating a new folder and some files

$Path = 'C:\Foo\Secure'
New-Item -Path $Path -ItemType directory -ErrorAction SilentlyContinue  |
  Out-Null
1..3 | ForEach-Object {
  "Secure File" | Out-File "$Path\SecureFile$_.txt"
}

Viewing files in $Path folder

$Files = Get-ChildItem -Path $Path
$Files | Format-Table -Property Name, Attributes

Encrypting the files

$Files| ForEach-Object Encrypt

Viewing file attributes

Get-ChildItem -Path $Path |
  Format-Table -Property Name, Attributes

Decrypting the files

$Files| ForEach-Object {
  $_.Decrypt()
}

Viewing the file attributes

Get-ChildItem -Path $Path |
  Format-Table -Property Name, Attributes

How it works...

In step 1, you start the Windows Notepad application. There is no console output, but you do see Notepad pop up like this:

Figure 3.19: Starting NotePad

In step 2, you obtain the methods from the object representing the Notepad process, with output like this:

Figure 3.20: Viewing Notepad’s methods

With step 3, you use the process’s Kill() method to kill the Notepad process, which generates no console output, but you see the Notepad window disappear. In step 4, you confirm that the Notepad process no longer exists, with output like this:

Figure 3.21: Ensuring the Notepad process no longer exists

To illustrate encrypting and decrypting files using .NET methods, in step 5, you create a new folder and some files which creates no output. In step 6, you view the attributes of the three files created, with output like this:

Figure 3.22: Viewing newly created files and their attributes

In step 7, you use the Encrypt() method to encrypt the files, generating no output. In step 8, you view the files and their attributes again, with this output:

Figure 3.23: Viewing encrypted files and file attributes

In step 9, you decrypt the previously encrypted files, generating no output. In the final step, step 10, you examine the file attributes to ensure that the files are now decrypted, with the following results:

Figure 3.24: Viewing decrypted files and file attributes

There's more...

In step 2, you view the methods you can invoke on a process object. As shown in the figure, one of those methods is the Kill() method. In step 3, you use that method to stop the Notepad process. The Kill() method is an instance method, - meaning you invoke it to kill (stop) a specific process. You can read more about this .NET method at https://docs.microsoft.com/dotnet/api/system.diagnostics.process.kill.

The output in step 4 illustrates an error occurring within VS Code. If you use the PowerShell 7 console, you may see a slightly different output, although with the same actual error message.

In steps 7 and step 9, you use the FileInfo objects (created by Get-ChildItem) and call the Encrypt() and Decrypt() methods. These steps demonstrate using a .NET method to achieve some objectives without specific cmdlets. Best practice suggests always using cmdlets where you can. You should also note that the syntax in the two steps is different. In step 7, you use a more modern syntax which calls the Encrypt method for each file. In step 9, you use an older syntax that does the same, albeit with more characters. Both syntax methods work.

In step 7, while you get no console output, Windows may generate a popup (aka Toast) to tell you that you should back up your encryption key. Backing up the key is a good idea. If you lose the key, you lose all access to the file(s) – so be very careful.

Creating a C# Extension

For most day-to-day operations, the commands provided by PowerShell, from Windows features, or third-party modules, provide all the functionality you need to manage your systems. In some cases, as you saw in “Leveraging .NET Methods,” commands do not exist to achieve your goal. In those cases, you can use the methods provided by .NET.

There are also cases where you need to perform more complex operations without PowerShell cmdlet or direct .NET support. You may, for example, have a component of an ASP.NET web application written in C#, but you now wish to repurpose it for administrative scripting purposes.

PowerShell makes it easy to add a class, based on .NET language source code, into a PowerShell session. You supply the C#’’ code, and PowerShell creates a .NET class that you can use the same way you use .NET methods (and using virtually the same syntax). You use the Add-Type cmdlet and specify the C# code for your class/type(s). PowerShell compiles the code and loads the resultant class into your PowerShell session.

An essential aspect of .NET methods is that a method can have multiple definitions or calling sequences. Known and method overloads, these different definitions allow you to invoke a method using different sets of parameters. This is not dissimilar to PowerShell’s use of parameter sets. For example, the System.String class, which PowerShell uses to hold strings, contains the Trim() method. You use that method to remove extra characters, usually space characters, from the start or end of a string (or both). The Trim() method has three different definitions, which you view in this recipe. Each overloaded definition trims characters from a string slightly differently. To view more details on this method, and the three overloaded definitions, see https://docs.microsoft.com/dotnet/api/system.string.trim

In this recipe, you create and use two simple classes with static methods.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Examining overloaded method definition

("a string").Trim

Creating a C# class definition in here string

$NewClass = @"
namespace Reskit {
   public class Hello {
     public static void World() {
         System.Console.WriteLine("Hello World!");
     }
   }
  }
"@

Adding the type into the current PowerShell session

Add-Type -TypeDefinition $NewClass

Examining method definition

[Reskit.Hello]::World

Using the class's method

[Reskit.Hello]::World()

Extending the code with parameters

$NewClass2 = @"
using System;
using System.IO;
namespace Reskit {
  public class Hello2  {
    public static void World() {
      Console.WriteLine("Hello World!");
    }
    public static void World(string name) {
      Console.WriteLine("Hello " + name + "!");
    }
  }
}
"@

Adding the type into the current PowerShell session

Add-Type -TypeDefinition $NewClass2

Viewing method definitions

[Reskit.Hello2]::World

Calling with no parameters specified

[Reskit.Hello2]::World()

Calling new method with a parameter

[Reskit.Hello2]::World('Jerry')

How it works...

In step 1, you examine the definitions of the Trim() method for a string, with output like this:

Figure 3.25: Viewing the Trim() methods on System.String

In step 2, you create a C# class definition for the Hello class that contains one method (World()). In step 3, you add this class to the current PowerShell session. These two steps create no output at the console.

In step 4, you examine the new class’s method signature with output like this:

Figure 3.26: Viewing the World() method definition on the newly created class

In step 5, you use the method, with output like this:

Figure 3.27: Using the World() method

In step 6, you define a more complex class definition for the Hello class, this time with two method definitions for the World method. In step 7, you add this new class definition to your current PowerShell session. These two steps generate no console output.

In step 8, you examine the two method signatures with output like this:

Figure 3.28: Viewing the new method definitions for Hello2

In step 9, you invoke the World() method on the Hello2 class – with output like this:

Figure 3.29: Viewing the new method definitions for Hello2

In step 10, you call the new method but specify a string as a parameter. The output of this step is as follows:

Figure 3.30: Calling the new method with a parameter

There's more...

There are several ways you discover method overloads. In step 1, you examine the different definitions of a method, the Trim() method of the System.String class. This step creates a pseudo-object containing a string and then looks at the method definitions for that class.

PowerShell creates an unnamed object (in the .NET Managed heap) in this step. As soon as the command is complete, .NET marks the memory as reclaimable. At some point later, .NET runs the GC process to reclaim the memory used by this temporary object and re-organizes the managed heap.

As you can see, there are three overloaded definitions – three different ways you can invoke the Trim() method. You use the first overloaded definition to remove both leading and trailing space characters from a string, probably the most common usage of Trim(). With the second definition, you specify a specific character and .NET removes any leading or trailing occurrences of that character. With the third definition, you specify an array of characters to trim from the start or end of the string. The last two definitions are less useful in most cases, although it depends on your needs. The extra flexibility is useful.

In step 3, you used the Add-Type cmdlet to add the class definition into your current workspace. The cmdlet compiles the C# code, creates, and then loads a DLL, enabling you to use the classes. If you intend to use this add-in regularly, you could add steps 2 and step 3 to your PowerShell profile file. Alternatively, if you use this method within a script, you could add the steps to the start of the script.

As you can see from the C# code in step 6, you can add multiple definitions to a method. Overloaded methods can provide great value for both the classes within .NET or in your code. The .NET method documentation, which you can find at https://docs.microsoft.com, is mainly oriented toward developers rather than IT Professionals. If you have issues with a .NET method, feel free to fire off a query on one of the many PowerShell support forums, such as the PowerShell group at Spiceworks: https://community.spiceworks.com/programming/powershell.

Creating a cmdlet

As noted previously in this chapter, for most operations, the commands and cmdlets available to you natively provide all the functionality you need. In the “Creating a C# Extension” recipe, you saw how you could create a class definition and add it to PowerShell. In some cases, you may wish to expand on the class definition and create a custom cmdlet.

Creating a compiled cmdlet requires you to either use a tool such as Visual Studio or use the free tools provided by Microsoft as part of the .NET Core Software Development Kit (SDK). The free tools in the SDK are more than adequate to help you to create a cmdlet using C#. Microsoft’s Visual Studio, whether the free community edition or the commercial releases, is a rich and complex tool whose inner workings are well outside the scope of this book.

As in the “Creating a C# Extension,” an important question you should be asking is when/why you should create a cmdlet? Aside from the perennial “because you can” excuse, there are reasons why an extension or a cmdlet might be a good idea. You can create a cmdlet to improve performance., In some cases, a compiled cmdlet is faster than doing operations using a PowerShell script. For some applications, using a cmdlet is to perform a specific action may difficult or not possible directly. PowerShell does not have support for asynchronous operations and Language Independent Query (LINQ), which are more straightforward in C#. A developer could write the cmdlet in C#, allowing you to use it easily with PowerShell.

To create a cmdlet, you need to install the Windows Software Development Kit (SDK). The SDK is a free download you get via the Internet. The SDK contains all the tools you need to create a cmdlet. Sadly there is no easy way to download and install the SDK programmatically – you need to use the Windows GUI – you have to use your browser and navigate to a web page, then download and run the SDK installer.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Installing the .NET SDK

Creating the cmdlet folder

New-Item -Path C:\Foo\Cmdlet -ItemType Directory -Force
Set-Location C:\Foo\Cmdlet

Creating a new class library project

dotnet new classlib --name SendGreeting

Viewing contents of new folder

Set-Location -Path .\SendGreeting
Get-ChildItem

Creating and displaying global.json

dotnet new globaljson
Get-Content -Path .\global.json

Adding PowerShell package

dotnet add package PowerShellStandard.Library

Create the cmdlet source file

$Cmdlet = @"
using System.Management.Automation;  // Windows PowerShell assembly.
namespace Reskit
{
  // Declare the class as a cmdlet
  // Specify verb and noun = Send-Greeting
  [Cmdlet(VerbsCommunications.Send, "Greeting")]
  public class SendGreetingCommand : PSCmdlet
  {
    // Declare the parameters for the cmdlet.
    [Parameter(Mandatory=true)]
    public string Name
    {
      get { return name; }
      set { name = value; }
    }
    private string name;
    // Override the ProcessRecord method to process the
    // supplied name and write a geeting to the user by
    // calling the WriteObject method.
    protected override void ProcessRecord()
    {
      WriteObject("Hello " + name + " - have a nice day!");
    }
  }
}
"@
$Cmdlet | Out-File .\SendGreetingCommand.cs

Removing the unused class file

Remove-Item -Path .\Class1.cs

Building the cmdlet

dotnet build

Importing the DLL holding the cmdlet

$DLLPath = '.\bin\Debug\net6.0\SendGreeting.dll'
Import-Module -Name $DLLPath

Examining the module's details

Get-Module SendGreeting

Using the cmdlet

Send-Greeting -Name 'Jerry Garcia'

How it works...

In step 1, you install the .NET SDK. You begin by using the browser to navigate to the .NET download site, which looks like this:

Figure 3.31: Downloading the .NET SDK

If you click on the “Download .NET SDK” button, your browser should download the installation program. When the browser finishes the download, you should see something like this:

Figure 3.32: Downloading the .NET SDK

Click on the “Open File” to run the .NET SDK installer. You should now see an option to install the SDK like this:

Figure 3.33: Installing the .NET SDK

When you click on the Install button, the installation program installs the SDK and, when complete, shows you the following dialog box:

Figure 3.34: Completing the installation of the .NET SDK

Once you have the SDK installed, you need to restart PowerShell to carry out the remainder of this recipe.

In step 2, you create a new folder (C:\Foo\Cmdlet) which you use for developing your cmdlet. This step generates the following output:

Figure 3.35: Creating the cmdlet folder

In step 3, you create a new class library project for your cmdlet, which generates the following output:

Figure 3.36: Creating a new .NET project for your cmdlet

In step 4, you view the contents of the folder contents, which look like this:

Figure 3.37: Viewing the contents of the cmdlet project’s folder

To tell the .NET build engine which version of .NET to compile your cmdlet against, you create a global.json file within your project, as shown in step 5. The step displays the following output

Figure 3.38: Creating and viewing the global.json file

In step 6, you add the PowerShell package to the cmdlet’s project with the following output:

Figure 3.39: Adding the PowerShell package to the cmdlet’s project

In step 7, you create a source file containing your cmdlet’s C# code. In step 8, you remove an unneeded class file previously created by the dotnet program. These two steps produce no output.

In step 9, you use the dotnet.exe program to build your cmdlet, with output like this:

Figure 3.40: Building your cmdlet

In step 10, you import the DLL, built by the previous step, that holds your cmdlet. This step creates no output. PowerShell treats this DLL as a binary Powershell module, and in step 11, you use the Get-Module cmdlet to see the module’s details, with output like this:

In step 12, you use your cmdlet with a value specified for the cmdlet’s -Name parameter, with output like this:

Figure 3.42: Using the Send-Greeting cmdlet

There's more...

In step 1, you download the .NET 6.0 SDK. You need this tool kit to create a cmdlet. There is no obvious way to automate the installation, so you need to run the installer and click through its GUI to install the .NET SDK.

In step 3, you create a new class library project. This step also makes the SendGreeting.csproj file, as you can see. This file is a Visual Studio .NET C# project file that contains details about the files included in your cmdlet project, assemblies referenced from your code, and more. For more information on the project file, see https://docs.microsoft.com/en-us/aspnet/web-forms/overview/deployment/web-deployment-in-the-enterprise/understanding-the-project-file.

In step 5, you create the global.json file. If you do not create this file, the dotnet command will compile your cmdlet with the latest version of .NET loaded on your system. Using this file tells the project build process to use a specific version of .NET Core (such as version 6.0). For an overview of this file, https://docs.microsoft.com/dotnet/core/tools/global-json.

In step 9, you compile your source code file and create a DLL containing your cmdlet. This step compiles all the source code files in the folder to create the DLL. Therefore. you could have multiple cmdlets in separate source code files and build the entire set in one operation.

In step 11, you can see that the module, SendGreeting, is a binary module. A binary module is one just loaded directly from a DLL. Using DLLs as binary modules is fine for testing, but in production, You can get more details on manifest files from https://docs.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest. You should also move the module, module manifest, and any other module contents such as help files, to a supported module location. You might also consider publishing the completed module to either the PowerShell gallery or your internal repository.

4 Managing Active Directory

Feedback

We are constantly looking at improving our content, so what could be better than listening to what you as a reader have to say? Your feedback is important to us and we will do our best to incorporate it. Could you take two mins to fill out the feedback form for this book and let us know what your thoughts are about it? Here's the link: https://packt.link/A8yX4.

Thank you in advance.

This chapter covers the following recipes:

Installing a Forest Root Domain

Testing an AD Installation

Installing a Replica Domain Controller

Installing Child Domain

Creating and Managing AD Users and groups

Managing AD Computers

Adding/Removing users using a CSV file

Creating a Group Policy Objects

Reporting on AD Computers

Reporting on AD Users

Managing AD Replication

Introduction

A core component of almost all organizations’ IT infrastructure is Active Directory (AD). AD provides access control, user and system customization, and a wealth of directory and other services. Microsoft first introduced AD with Windows 2000 and has improved and expanded the product with each successive release of Windows Server.

At the core is Active Directory Domain Services (AD DS). Over the years, Microsoft has made “AD” more of a brand than a single feature. There are four additional Windows Server features under the AD brand:

AD Certificate Services (AD-CS) - this allows you to issue X.509 certificates for your organization. For an overview of AD-CS, see https://docs.microsoft.com/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831740(v=ws.11).

AD Federation Services (AD-FS) - this feature enables you to federate identity with other organizations to facilitate interworking. You can find an overview of AD-FS at https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831502(v=ws.11).

AD Lightweight Directory Services (AD-LDS) - This provides rich directory services for use by applications. You can find an overview of AD-LDS at https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831593(v=ws.11).

AD Rights Management Services (AD-RMS) - RMS enables you to control the rights to document access to limit information leakage. For an overview of RMS, see https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831364(v=ws.11).

Note that the overview documents above are older documents based on Windows Server 2012. The documentation teams have not updated them fully to reflect the latest Windows Server version at the time of writing. The overview and the essential operation of these features remain mostly unchanged, and the documents remain a good reference for each AD feature.

Active Directory’s domain service is complex, and there are a lot of moving parts. With AD, you have a logical structure consisting of Forests, Domains, Domain Trees, and Organisational Units. You also have the physical structure, including Domain Controllers and Global Catalog Servers. There is also a replication mechanism to replicate objects across your domain. For a deeper look at the architecture of AD, see https://activereach.net/support/knowledge-base/connectivity-networking/understanding-active-directory-its-architecture/.

A forest is a top-level container that houses one or more domains. A forest is a security boundary. If you need to operate between forests, you can set up cross-forest trusts to enable interworking between multiple forests. AD used DNS names as the basis for forest and domain names, and both AD domain controllers and AD clients use DNS to find the IP address of domain controllers. For this reason, DNS is a critical part of your AD infrastructure.

A domain is a collection of objects, including users, computers, policies, etc. You create a forest by installing the forest’s first domain controller. In AD domains, trees are collections of domains that you group in a hierarchical structure. Most organizations use a single domain (and thus a single domain tree) within a single forest. For very large organizations, another common approach is to have a single (empty) forest root domain and multiple child domains (typically based on geography. AD supports having multiple non-contiguous domain trees with a forest, but the best practice is to avoid them.

A domain controller (DC) is a Windows Server running AD and holding the objects for a given domain. ALL domains must have at least one DC, although best practice is always to have at least two. You install the AD DS service onto your server, then promote the server to be a DC. You can also have DCs within a forest running older versions of Windows Server.

The global catalog (GC) is a partial replica of objects from every domain in an object to enable searching. Microsoft Exchange, for example, uses the GC heavily. You can have the GC service on some or all DCs in your forest. Generally, you install the GC facility while promoting a Windows Server to be a DC, but fewer GCs might be preferable to reduce replication traffic for very large domains.

Using AD Domain Services (or AD) and PowerShell, you can deploy your domain controllers throughout your organization. Use the “Installing Active Directory and forest root domain” recipe to install a forest root domain controller and establish an AD Forest. That recipe converts DC1 as a workgroup server into a DC – the first DC in the Reskit.Org forest.

You can install Windows features and manage services using PowerShell in a domain environment using PowerShell remoting which requires authentication. From one machine, you use PowerShell remoting to perform operations on other systems, and for that, you need the correct credentials for the remote machine. Sometimes, you may encounter the well-understood Kerberos double hop problem for which the Credential Security Subsystem Provider can be a solution. For a better explanation of this issue, see https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/ps-remoting-second-hop?view=powershell-7.1.

In this book, you use a domain, Reskit.Org, for most of the recipes. In this chapter, you establish the forest root domain and a child domain). You also create users, groups, and Organizational units, which you rely on in later chapters.

Once you have a first DC in your Reskit.Org forest, you should add a replica DC to ensure reliable domain operations. In “Installing a replica domain controller,” you add a second DC to your domain. In “Installing a child domain,” you extend the forest and add a child domain to your forest.

The AD service uses a database of objects, including users, computers, and Groups. In the “Managing AD users and groups” recipe, you create, move, and remove user and group objects and create and use Organizational Units (OUs). In “Managing Active Directory computers,” you manage the computers in your Active Directory, including joining workgroup systems to the domain. In the “Adding/Removing users using CSV files” recipe, you add users to your AD using a comma-separated value file containing users’ details.

Group policy is another important feature of Active Directory. With Group Policy, you can define policies for users and computers that Windows applies automatically to the user and/or computer. In the “Creating Group Policy Objects” recipe, you create a simple GPO and apply that policy.

Active Directory can use multiple DCs for both load balancing and fault tolerance. These DCs must be synchronized across the forest whenever you change any AD object. AD Replication performs that function. In “Managing AD replication,” you examine tools to help you manage and troubleshoot replication. For a fuller look at AD replication, see https://social.technet.microsoft.com/wiki/contents/articles/4592.how-active-directory-replication-works.aspx.

In the recipes “Reporting on AD computers” and “Reporting on AD Users,” you examine the AD to find details on computers that have not started up or logged onto the domain. You also look at user accounts for users who are members of special security groups (such as enterprise administrators). These two recipes help keep your AD free of stale objects and reduce potential security risks.

The systems used in the chapter

The recipes in this chapter create the Reskit.Org forest with a parent and a child domain and a total of three DCs as follows:

Figure 4.1: Host in use for this chapter

In later chapters, you will use additional servers and will promote SRV1 to be a domain-based server rather than being in a workgroup.

Installing a Forest Root Domain

Installing Active Directory and DNS has always been reasonably straightforward. You can always use the Server Manager GUI, but using PowerShell is easier to automate. You create an AD forest by creating your first domain controller.

To create a DC, you start with a system running Windows Server. You then add the AD DS services Windows feature to the server and the management tools. Then you use the management tools to promote DC1 to be your first DC (aka DC1.Reskit.Org) within the Reskit.Org domain.

Getting ready

You run this recipe on DC1 after installing PowerShell 7 and VS Code.

How to do it...

Installing the AD Domain Services feature and management tools

Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools

Importing the ADDeployment module

Import-Module -Name ADDSDeployment

Examining the commands in the ADDSDeployment module

Get-Command -Module ADDSDeployment

Creating a secure password for the Administrator

$PSSHT = @{
  String      = 'Pa$$w0rd'
  AsPlainText = $true
  Force       = $true
}
$PSS = ConvertTo-SecureString @PSSHT

Testing DC Forest installation starting on DC1

$FOTHT = @{
  DomainName           = 'Reskit.Org'
  InstallDNS           = $true
  NoRebootOnCompletion = $true
  SafeModeAdministratorPassword = $PSS
  ForestMode           = 'WinThreshold'
  DomainMOde           = 'WinThreshold'
}
Test-ADDSForestInstallation @FOTHT -WarningAction SilentlyContinue

Creating Forest Root DC on DC1

$ADHT = @{
  DomainName                    = 'Reskit.Org'
  SafeModeAdministratorPassword = $PSS
  InstallDNS                    = $true
  DomainMode                    = 'WinThreshold'
  ForestMode                    = 'WinThreshold'
  Force                         = $true
  NoRebootOnCompletion          = $true
  WarningAction                 = 'SilentlyContinue'
}
Install-ADDSForest @ADHT

Checking key AD and related services

Get-Service -Name DNS, Netlogon

Checking DNS zones

Get-DnsServerZone

Restarting DC1 to complete promotion

Restart-Computer -Force

How it works...

In step 1, you install the AD Domain Services feature. This feature enables you to deploy a server as a domain controller. The output of this command looks like this:

Figure 4.2: Host in use for this chapter

In step 2, you manually import the ADDSDeployment module. Since PowerShell does not support this module natively, this step loads the module using the Windows PowerShell compatibility feature, which is why you see a warning message in the output of this command, which looks like this:

Figure 4.3: Importing the ADDSDeployment module

In step 3, you use the Get-Command cmdlet to discover the commands contained in the ADDSDeployment module, which looks like this:

Figure 4.4: Discovering the commands within the ADDSDeployment module

With step 4, you create a secure string password to use as the Administrator password in the domain you are creating. This step produces no output.

Before you promote a server to be a DC, it can be useful to test to ensure that a promotion would be successful as far as possible. In step 5, you use the Test-ADDSForestInstallation command to check whether you can promote DC1 to be a DC in the Reskit.Org domain. The output of this command looks like this:

Figure 4.5: Testing DC forest installation

In step 6, you promote DC1 as the first domain controller in a new domain, Reskit.Org. The output looks like this:

Figure 4.6: Testing DC forest installation

With the promotion complete, you can check the critical services required for Active Directory. Checking the Netlogon and DNS Services, which you do in step 7, should look like this:

Figure 4.7: Checking the DNS and Netlogon services

In step 8, you use the Get-DnsServerZone to see the zones currently available on DC1 and their status. Before rebooting after the DC promotion, the output from this step looks like this:

Figure 4.8: Checking the DNS zones available

In step 9, you run the Restart-Computer command, which generates no actual output. To complete the promotion process, you need to reboot DC1. After the reboot, you should be able to log in to the Reskit.Org domain as the Domain Administrator (using the password specified in this recipe).

There's more...

In step 1, you install the ADDS feature and the management tools, including the ADDSDeployment module. In step 2, you load the module, which, as you can see, PowerShell loads using the Windows PowerShell compatibility feature. Although the modules' commands work, the output does not appear as nicely formatted as you might observe running these commands within Windows PowerShell. The Import-Module command does not import the module’s display XML, but you can do that manually should you need to. As shown, you can always import the XML manually. The key thing to look at is the success or failure of the key deployment steps, so this is probably not a major problem (and there is a workaround!).

Throughout the recipes in this book, you use the string “Pa$$w0rd” as a password for each host and for most core AD users you create. The book uses these steps and this password for simplicity - since the Reskit.Org is not a real production domain the risks are minimal. The steps that contain a plain text version of a weak password, as you see in step 4, are not a best practice for production.

In step 5, you test whether Windows can promote DC1 to be a DC. As you can see from the message, the command completed the test successfully. This success means that you can proceed to install AD on this host. And in step 8, you run the Install-ADDSForest to promote DC1. By default, this step would cause the host to reboot after completing the promotion process, but in this case, you do not reboot. This approach allows you to examine the results of the step before rebooting.

In step 7, you can see that the DNS service is available and running. The netlogon service, which is

In step 8, you see the zones available on DC1’s DNS service. You installed this service when you promoted DC1. As you can see, before the reboot, the DNS service is running, but the Reskit.Org zone is not AD integrated because there is no AD service running. After the reboot, this property should change.

Testing an AD installation

In “Installing an Active Directory Forest Root Domain,” you installed AD on DC1. In that recipe, you installed AD initially, without rebooting, then did some basic testing, followed by a reboot. After the required reboot, it is useful to check to ensure that your domain and domain controller are fully up, running, and working correctly. In this recipe, you examine core aspects of the AD infrastructure on your first DC.

Getting ready

You run this recipe on DC1, the first domain controller in the Reskit.Org domain, after you have promoted it to be a DC. You promoted DC1 as a domain controller in the Reskit.Org domain in “Installing an Active Directory forest root domain.” Log on as Reskit\Administrator using the password set in the previous recipe, Pa$$w0rd.

How to do it...

Examining Root Directory Service Entry (DSE)

Get-ADRootDSE -Server DC1.Reskit.Org

Viewing AD forest details

Get-ADForest

Viewing AD Domain details

Get-ADDomain -Current LocalComputer

Checking Netlogon, ADWS, and DNS services

Get-Service NetLogon, ADWS, DNS

Getting initial AD users

Get-ADUser -Filter * |
  Sort-Object -Property Name |
    Format-Table -Property Name, DistinguishedName

Getting initial AD groups

Get-ADGroup -Filter *  |
  Sort-Object -Property Groupscope, Name |
    Format-Table -Property Name, GroupScope

Examining Enterprise Admins group membership

Get-ADGroupMember -Identity 'Enterprise Admins'

Checking DNS zones on DC1

Get-DnsServerZone -ComputerName DC1

Testing domain name DNS resolution

Resolve-DnsName -Name Reskit.Org

How it works...

In step 1, after you completed the installation of AD and rebooted DC1 (and logged in), you examine the AD Directory Service Entry (DSE), which looks like this:

Figure 4.9: Examining the Root DSE

In step 2, you use Get-ADForest to return core information about the Reskit.Org forest, with output like this:

Figure 4.10: Viewing AD Forest details

In step 3, you use the Get-ADDomain command to get details about the Reskit.Org domain, with output like this:

Figure 4.11: Viewing AD Domain details

In step 4, you check on three key services for AD: Active Directory Web Services, DNS, and Netlogon services. You see output like this:

Figure 4.12: Viewing status of key services

In step 5, you view the AD users created by the AD installation process, with output like this:

Figure 4.13: Viewing initial AD users

When you created the forest, the AD Installer also created a large number of AD groups which you view in step 6. The output of this step looks like this:

Figure 4.14: Viewing initial AD groups

The Enterprise Admins group is a very high-security group within AD. Membership of this group gives significant rights to systems and services within a domain. In step 7, you examine the initial membership of this group, with output like this:

Figure 4.15: Viewing Enterprise Admin group membership

In step 8, after you rebooted a workgroup server to become a domain controller, you recheck the DNS zones available from DC1, with output like this:

Figure 4.16: Checking DNS zones

A final check, in step 9, is to test DNS to ensure that you can resolve the AD domain name. The output resembles this:

Figure 4.17: Checking DNS resolution

There's more...

In step 1, you viewed the Root DSE for the Reskit.Org domain. The Active Directory implements a Lightweight Directory Access Protocol (LDAP) directory service for domain activities. The DSE is a component of LDAP directories and contains information about your directory structure. Windows allows access to the DSE without requiring authentication. The DSE includes much information about your AD forest, which might help an attacker. Thus, the best practice is never to expose an AD DC to the Internet if you can possibly help it. For a more detailed look at the Root DSE, see https://docs.microsoft.com/windows/win32/adschema/rootdse.

The ADWS service, which you investigate in step 4, implements an important web service. The AD PowerShell commands use this web service to get information and make changes to your AD. If this service is not running, AD commands do not work. You should always check to ensure the service has started before using the AD cmdlets.

In step 6, you saw the groups created by the promotion process. These groups have permissions associated and thus are useful. Before adding users to these groups, consider reviewing the group and determining (and possibly changing) the permissions and rights assigned to these groups.

In step 8, you examine the DNS zones available from DC1. As you can see, the Reskit.Org domain is now AD integrated. After creating the AD forest, Windows integrates the zone into the AD as part of the reboot.

In step 9, you check the resolution of the domain name. In this case, you see both IPv6 and IPv4 addresses.

Installing a replica Domain Controller

In “Installing an Active Directory forest root domain,” you installed AD on DC1. If you have just one DC, then that DC is a single point of failure. If the DC goes down with a single domain controller, you cannot manage or log in to the domain. It is always a best practice to install at least two DCs. If you are using VMs for your DCs, you should also ensure that each DC VM is on a separate virtualization host – otherwise, the VM host is a single point of failure.

To add a second DC to your domain, you run Install-ADDSDomainController on another host, that is DC2. This cmdlet is similar to the Install-ADDSForest in terms of parameters. It is useful to conduct tests to ensure the second DC’s promotion can succeed as with creating your first DC.

In this recipe, you promote a host, DC2, to be the second DC in the Reskit.Org domain. Like creating your first DC, after you promote DC2 to be a DC, you need to reboot the server before processing. And after the reboot, it is useful to ensure the promotion process was successful.

Getting ready

You run this recipe on DC2, a domain-joined server on which you have installed PowerShell 7 and VS Code. You should log into DC2 as Reskit\Administrator, a member of the Enterprise Admins group.

Note that if you are using the Reskit.Org build scripts from GitHub, make sure you build the DC2 M after you have created DC1 and promoted it to be a domain controller.

How to do it...

Importing the Server Manager module

Import-Module -Name ServerManager -WarningAction SilentlyContinue

Checking DC1 can be resolved

Resolve-DnsName -Name DC1.Reskit.Org -Type A

Testing the network connection to DC1

Test-NetConnection -ComputerName DC1.Reskit.Org -Port 445
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 389

Adding the AD DS features on DC2

Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools

Promoting DC2 to be a DC

Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
$URK    = "Administrator@Reskit.Org"
$PW     = 'Pa$$w0rd'
$PSS    = ConvertTo-SecureString -String $PW -AsPlainText -Force
$CredRK = [PSCredential]::New($URK,$PSS)
$INSTALLHT = @{
  DomainName                    = 'Reskit.Org'
  SafeModeAdministratorPassword = $PSS
  SiteName                      = 'Default-First-Site-Name'
  NoRebootOnCompletion          = $true
  InstallDNS                    = $false
  Credential                    = $CredRK
  Force                         = $true
}
Install-ADDSDomainController @INSTALLHT | Out-Null

Checking the computer objects in AD

Get-ADComputer -Filter *  |
  Format-Table DNSHostName, DistinguishedName

Rebooting DC2 manually

Restart-Computer -Force

Checking DCs in Reskit.Org

$SB = 'OU=Domain Controllers,DC=Reskit,DC=Org'
Get-ADComputer -Filter * -SearchBase $SB  -Properties * |
  Format-Table -Property DNSHostName, Enabled

Viewing Reskit.Org domain DCs

Get-ADDomain |
  Format-Table -Property Forest, Name,
                         ReplicaDirectoryServers

How it works...

In step 1, you import the ServerManager module, which creates no output. With step 2, you ensure that you can resolve your DC’s address, which is, at this point, the only DC in the Reskit.Org domain. The output looks like this:

Figure 4.18: Resolving DC1’s DNS entry

After confirming that you can resolve the IP address of DC1, in step 3, you check if you can connect to two key ports on DC1 (445 and 389). The output looks like this:

Figure 4.19: Checking key ports are available on DC1

As you saw, when promoting DC1 to be your first DC, you need to add the ADDSDeployment Windows feature to DC2 before you can promote the DC. You add this feature in step 4, with output that looks like this|:

Figure 4.20: Adding AD Domain Sevices to DC2

In step 5, you promote DC2 to be a DC and ensure that the DC does not reboot after this step completes. There is no output from this step.

Before rebooting DC2 to complete the DC promotion process, in step 6, you check to see the computer objects now in the AD, including your new replica DC. This step ensures that DC2 is now in the correct place in AD. The output of this step is like this:

Figure 4.21: Adding AD Domain Sevices to DC2

In step 7, you reboot DC2, which produces no console output, to complete the promotion of this host to be a replica DC.

After DC2 completes the reboot process, you log in as the domain administrator (Reskit\Administrator). After you log in, in step 8, you use Get-ADComputer to check the hosts in the Domain Controllers OU. You can see that the OU contains both DC1 and DC2. You see output like this:

Figure 4.22: Viewing AD Computers in the Domain Controllers OU

In the final step in this recipe, step 9, you use the Get-ADDomain command to view the details of the forest, the domain, and the replica DCs, with otuput like this:

Figure 4.23: Viewing Forest, Domain, and replica DC information

There's more...

In step 1, you import the ServerManager module manually. This step loads the module using the Windows PowerShell compatibility solution described earlier. As you have seen in earlier recipes, this module is not supported natively with PowerShell 7.

In step 3, you check to ensure DC2 can connect to DC1 over ports 445 and 389. Windows uses TCP port 445 for SMB file sharing. The Group Policy Agent on each domain-joined host uses this port to retrieve Group Policy details from the SYSVOL share on DC1. LDAP uses port 389 to perform actions against a DC, such as adding a new user or checking group membership. Both ports need to be open and contactable if the promotion of DC2 is to be successful.

In step 6, you retrieve all computer accounts currently in your AD. The AD DC promotion process ensures that this host’s computer account is now in the Domain Controllers OU by default. The location of a DC in an OU is essential as it enables your new DC to apply the group policy settings on this OU. If DC2 was a workgroup computer and not joined to the domain, the promotion process creates this account in the appropriate Domain Controllers OU. If DC2 were a domain member, then the promotion process would move the computer account into the Domain Controllers OU.

Mistakes are easy to make, but also (usually) easy to correct. In this recipe, before promoting DC2 to be a DC, you check to ensure that the prerequisites are in place before the promotion. Then you confirm that you have configured your forest, domain, and domain controllers correctly. Likewise, the checks in the last two steps are not strictly necessary. If nothing else, just more evidence of a successful creation of a replica DC.

These steps are probably redundant since most AD promotions ‘just work’. Nevertheless, the additional checks ensure you discover and resolve any issues that might affect the promotion or cause the AD to not work correctly after a not-fully successful promotion (for whatever reason).

Installing a Child domain

In “Installing a replica domain controller,” you added a DC to an existing domain. With all the prerequisites like DNS in place, the promotion process is simple and quick.

An AD forest can contain more than one domain, with one domain having zero, one, or more child domains. This architecture provides for delegated administration and reduction in replication traffic across a global network. Like creating a replica DC, creating a new child domain is simple, as you can see in this recipe.

Best practice calls for a contiguous namespace of domains, where the additional domain is a child of another existing domain. In this recipe, you create a child domain, UK.Reskit.Org. You begin with the domain joined server UKDC1 and with this recipe, covert it to be the first DC in a new child domain UK.Reskit.Org. In doing so, the hostname changes from UKDC1.Reskit.Org to UKDC1.UK.Reskit.Org.

The steps in this recipe are very similar to those in “Installing a replica directory domain controller.”

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Importing the ServerManager module

Import-Module -Name ServerManager -WarningAction SilentlyContinue

Checking DC1 can be resolved

Resolve-DnsName -Name DC1.Reskit.Org -Type A

Checking network connection to DC1

Test-NetConnection -ComputerName DC1.Reskit.Org -Port 445
Test-NetConnection -ComputerName DC1.Reskit.Org -Port 389

Adding the AD DS features on UKDC1

$Features = 'AD-Domain-Services'
Install-WindowsFeature -Name $Features -IncludeManagementTools

Creating a credential and installation hash table

Import-Module -Name ADDSDeployment -WarningAction SilentlyContinue
$URK    = "Administrator@Reskit.Org"
$PW     = 'Pa$$w0rd'
$PSS    = ConvertTo-SecureString -String $PW -AsPlainText -Force
$CredRK = [PSCredential]::New($URK,$PSS)
$INSTALLHT    = @{
  NewDomainName                 = 'UK'
  ParentDomainName              = 'Reskit.Org'
  DomainType                    = 'ChildDomain'
  SafeModeAdministratorPassword = $PSS
  ReplicationSourceDC           = 'DC1.Reskit.Org'
  Credential                    = $CredRK
  SiteName                      = 'Default-First-Site-Name'
  InstallDNS                    = $false
  Force                         = $true
}

Installing child domain

Install-ADDSDomain @INSTALLHT

Looking at the AD forest

Get-ADForest -Server UKDC1.UK.Reskit.Org

Looking at the UK domain

Get-ADDomain -Server UKDC1.UK.Reskit.Org

Checking on user accounts in UK domain

Get-ADUser -Filter *  |
  Format-Table -Property SamAccountName, DistinguishedName

Checking on user accounts in parent domain

Get-ADUser -Filter * -Server DC1.Reskit.Org |
  Format-Table -Property SamAccountName, DistinguishedName

How it works...

In step 1, you load the Server Manager module. As discussed in earlier recipes, this module is not natively supported by PowerShell 7. This step, which produces no output, loads the module using the Windows PowerShell compatibility solution.

When you create a new domain and new domain controller (using UKDC1, for example), the server needs to contact the holder of the Domain Naming FSMO role, the DC responsible for the forest’s changes. In step 2, you check to ensure you can resolve this host, DC1, which looks like this:

Figure 4.24: Viewing Forest, Domain, and replica DC information

In step 3, you check that you can connect to ports 445 and 389 on DC1. These two ports are fundamental to AD operations. The output looks like this:

Figure 4.25: Testing network connection to DC1

In step 4, you add the AD DS feature to UKDC1, with output like this:

Figure 4.26: Adding the AD DS feature to UKDC1

In step 5, you create a hash table of the parameters to create your child domain. This step creates no output. In step 6, you make UKDC1 the first DC in the UK.Reskit.Org domain, creating no console output. After this step, the host reboots, and you can login as UK\Administrator for the remainder of this recipe.

After rebooting, in step 7, you use Get-ADForest to view the details of the forest, including the GC servers in the forest. The output of this step looks like this:

Figure 4.27: Viewing AD Forest details

In step 8, you examine the details of the UK domain using Get-ADDomain, with output like this:

Figure 4.28: Viewing details of the UK domain

In step 9, you check the user account objects contained in the new UK domain, with output like this:

Figure 4.29: Viewing user accounts in the UK domain

In step 10, you check on the user accounts in the parent domain, Reskit.Org, with output like this:

Figure 4.30: Viewing user accounts in the Reskit.Org domain

There's more...

In step 2, you check that you can resolve the IP address for DC1, the domain naming FSMO role holder. You could call Get-ADForest and obtain the hostname from the DomainNamingMaster property.

In step 5, you promote UKDC1 to be the first DC in a new child domain, UK.Reskit.Org. Unlike in the two previous DC promotions (that is, promoting DC1 and DC2), in this step, you allow Windows to reboot after Windows completes the DC promotion. This step can take quite a while - potentially 10 minutes or more, so be patient.

In step 7, you use Get-ADForest to examine details of the forest as stored on UKDC1. As you can see in the figure, these details now show your new domain (UK.Reskit.Org) in the Domains property. Also, by default, you can see that UKDC1.UK.Reskit.Org is also a Global Catalog server.

Creating and Managing AD Users and groups

After you have created your forest/domain and your domain controllers, you can begin to manage the core objects in AD, namely, users, groups, computers, and organizational units (OUs). User and computer accounts identify a specific user or computer. Windows uses these objects to enable the computer and the user to log on securely using passwords held in the AD.

AD Groups enable you to collect users and computers into a single (group) account that simplifies setting access controls on resources such as files or file shares. As you saw in “Testing an AD installation,” the AD promotion process creates many potentially useful groups when you create a new forest.

Organizational Units (OUs) enable you to partition users, computers, and groups into separate container OUs. OUs provide you with essential roles in your AD. The first is role delegation. You can delegate the management of any OU (and child OUs) to be carried out by different groups. For example, you could create a top-level OU called UK in the Reskit.Org domain. You could then delegate permissions to the objects in this OU to a group, such as UKAdmins, enabling any group member to manage AD objects in and below the UK OU. You can delegate permissions to another group, such as the North America Admins group.

The second role played by OUs is to act as a target for group policy objects. You could create a group policy object for the IT team and apply it to the IT OU. You could create separate OU and GPOs that apply to only the computer and user objects in that OU. Thus, each user and computer in a given OU are configured based on the GPO.

In this recipe, you examine AD users and group objects. In a later recipe, “Managing AD computers,” you explore managing AD computers. And in “Creating Group Policy Objects,” you assign a Group Policy to an OU you create in this recipe.

Getting ready

You run this recipe on DC1, a domain controller in the Reskit.Org domain. You have previously installed PowerShell 7 and VS Code on this DC.

How to do it...

Creating a hash table for general user attributes

$PW  = 'Pa$$w0rd'
$PSS = ConvertTo-SecureString -String $PW -AsPlainText -Force
$NewUserHT = @{}
$NewUserHT.AccountPassword       = $PSS
$NewUserHT.Enabled               = $true
$NewUserHT.PasswordNeverExpires  = $true
$NewUserHT.ChangePasswordAtLogon = $false

Creating two new users

# First user
$NewUserHT.SamAccountName    = 'ThomasL'
$NewUserHT.UserPrincipalName = 'thomasL@reskit.org'
$NewUserHT.Name              = 'ThomasL'
$NewUserHT.DisplayName       = 'Thomas Lee (IT)'
New-ADUser @NewUserHT
# Second user
$NewUserHT.SamAccountName    = 'RLT'
$NewUserHT.UserPrincipalName = 'rlt@reskit.org'
$NewUserHT.Name              = 'Rebecca Tanner'
$NewUserHT.DisplayName       = 'Rebecca Tanner (IT)'
New-ADUser @NewUserHT

Creating an OU for IT

$OUHT = @{
    Name        = 'IT'
    DisplayName = 'Reskit IT Team'
    Path        = 'DC=Reskit,DC=Org'
}
New-ADOrganizationalUnit @OUHT

Moving users into the OU

$MHT1 = @{
    Identity   = 'CN=ThomasL,CN=Users,DC=Reskit,DC=ORG'
    TargetPath = 'OU=IT,DC=Reskit,DC=Org'
}
Move-ADObject @MHT1
$MHT2 = @{
    Identity = 'CN=Rebecca Tanner,CN=Users,DC=Reskit,DC=ORG'
    TargetPath = 'OU=IT,DC=Reskit,DC=Org'
}
Move-ADObject @MHT2

Creating a third user directly in the IT OU

$NewUserHT.SamAccountName    = 'JerryG'
$NewUserHT.UserPrincipalName = 'jerryg@reskit.org'
$NewUserHT.Description       = 'Virtualization Team'
$NewUserHT.Name              = 'Jerry Garcia'
$NewUserHT.DisplayName       = 'Jerry Garcia (IT)'
$NewUserHT.Path              = 'OU=IT,DC=Reskit,DC=Org'
New-ADUser @NewUserHT

Adding two users who get removed later

# First user to be removed
$NewUserHT.SamAccountName    = 'TBR1'
$NewUserHT.UserPrincipalName = 'tbr@reskit.org'
$NewUserHT.Name              = 'TBR1'
$NewUserHT.DisplayName       = 'User to be removed'
$NewUserHT.Path              = 'OU=IT,DC=Reskit,DC=Org'
New-ADUser @NewUserHT
# Second user to be removed
$NewUserHT.SamAccountName     = 'TBR2'
$NewUserHT.UserPrincipalName  = 'tbr2@reskit.org'
$NewUserHT.Name               = 'TBR2'
New-ADUser @NewUserHT

Viewing existing AD users

Get-ADUser -Filter *  -Property *|
  Format-Table -Property Name, Displayname, SamAccountName

Removing via a Get | Remove pattern

Get-ADUser -Identity 'CN=TBR1,OU=IT,DC=Reskit,DC=Org' |
    Remove-ADUser -Confirm:$false

Removing a user directly

$RUHT = @{
  Identity = 'CN=TBR2,OU=IT,DC=Reskit,DC=Org'
  Confirm  = $false}
Remove-ADUser @RUHT

Updating a user object

$TLHT =@{
  Identity     = 'ThomasL'
  OfficePhone  = '4416835420'
  Office       = 'Cookham HQ'
  EmailAddress = 'ThomasL@Reskit.Org'
  GivenName    = 'Thomas'
  Surname      = 'Lee'
  HomePage     = 'Https://tfl09.blogspot.com'
}
Set-ADUser @TLHT

Viewing updated user

Get-ADUser -Identity ThomasL -Properties * |
  Format-Table -Property DisplayName,Name,Office,
                         OfficePhone,EmailAddress

Creating a new domain local group

$NGHT = @{
 Name        = 'IT Team'
 Path        = 'OU=IT,DC=Reskit,DC=org'
 Description = 'All members of the IT Team'
 GroupScope  = 'DomainLocal'
}
New-ADGroup @NGHT

Adding all the users in the IT OU into the IT Team group

$SB = 'OU=IT,DC=Reskit,DC=Org'
$ItUsers = Get-ADUser -Filter * -SearchBase $SB
Add-ADGroupMember -Identity 'IT Team'  -Members $ItUsers

Display members of the IT Team group

Get-ADGroupMember -Identity 'IT Team' |
  Format-Table SamAccountName, DistinguishedName

How it works...

You use the New-ADUser cmdlet to create a new AD user. Due to the amount of information you wish to hold for any user you create, the number of parameters you might need to pass to New-ADUser can make for very long code lines. To get around that, you create a hash table of parameters and parameter values and then use it to create the user. In step 1, you create such a hash table, which produces no output.

In step 2, you add to the hash table you created in the previous step and create two new AD users. This step creates no output.

With step 3, you create a new Organizational Unit, IT, which produces no output. Next, in step 4, which also creates no output, you move the two previously created users into the IT OU. You use this OU to collect user and computer objects for the IT Department of Reskit.Org.

Rather than creating a user (which by default places the new user object in the Users container in your AD) and later moving it to an OU, you can create a new user object directly in an OU. In step 5, you create a third new user directly in the IT OU, which creates no output.

In step 6, which generates no output, you create two additional users, which you use later in the recipe.

In step 7, you retrieve all the AD users and display their Name, DisplayName, and SamAccountName properties, with output like this:

Figure 4.31: Viewing user accounts in the Reskit.Org domain

There are two broad patterns for removing a user. The first pattern (known as Get|Remove) has you find the AD user(s) you wish to remove and pipe them to the Remove-ADuser cmdlet. In step 8, you use this pattern to find, then remove the TBR1 user. This step produces no output.

With step 9, you remove a user by specifying the full distinguished name of the TBR2 user. This step also produces no output.

In step 10, you use the Set-ADUser to update the properties of a user, such as an email address, office, and home page. This step also produces no console output. After updating the user, in step 11, you view details about the user, as you can see here:

Figure 4.32: Viewing user accounts in the Reskit.Org domain

In step 12, you create a new Domain Local Group called “IT Team” within the IT OU. In step 13, you add all the users within the IT OU to this new group. These two steps produce no output

In step 14, you use Get-ADGroupMember to display details of the members of the group, with output like this:

Figure 4.33: Viewing Group Members

There's more...

In step 7, you see the users in the Reskit.Org domain. Note the user UK$. This user relates to the child domain (UK.Reskit.Org). It is not, as such, an actual user. The $ character at the end of the username indicates it’s a hidden user but fundamental to supporting the child domain. Don’t be tempted to tidy up and remove this user account - that would break the child domain structure.

As you see in this recipe, the cmdlets you use to add or modify a user, group, or OU create no output, which cuts down on the output you have to wade through. Some cmdlets and cmdlet sets would output details of the objects created, updated, or possibly deleted. Consider the lack of output for AD cmdlets as a feature!

Managing AD computers

Before you can log in to a host as a domain user, such as Reskit\JerryG, that host computer must be a domain member. In AD, computer objects represent domain-joined computers that can use the domain to authenticate user login. When a domain-joined computer starts up, it contacts a domain controller to authenticate itself. The computer logs into the domain and creates a secure channel to the DC. Once Windows establishes this secure channel, Windows can log on a user. Under the covers, Windows uses the secure channel to negotiate the user logon.

In terms of managing AD, computer objects are similar to user objects, albeit with different properties. The cmdlets to manage AD computer objects are similar to those used to manage AD user accounts.

You created and configured SRV1, a work group computer, in the recipes in earlier chapters. In this recipe, you work with AD computers and add SRV1 to the Reskit.Org domain. For the remainder of the chapters in this book, SRV1 is a member server, so you need to add this host to the Reskit.Org domain.

Getting ready

You run this recipe on DC1, a domain controller in the Reskit.Org domain. This recipe also uses SRV1, which, as noted above, starts as a non-domain joined Windows Server 2022 host. The recipe also uses UKDC1, the child domain’s DC. You have previously installed PowerShell 7 and VS Code on each host and login as Reskit\Administrator.

How to do it...

Getting computers in the Reskit Domain

Get-ADComputer -Filter * |
  Format-Table -Property Name, DistinguishedName

Getting computers in the UK Domain

Get-ADComputer -Filter * -Server UKDC1.UK.Reskit.Org |
  Format-Table -Property Name, DistinguishedName

Creating a new computer in the Reskit.Org domain

$NCHT = @{
    Name                   = 'Wolf'
    DNSHostName            = 'Wolf.Reskit.Org'
    Description            = 'One for Jerry'
    Path                   = 'OU=IT,DC=Reskit,DC=Org'
}
New-ADComputer @NCHT

Creating Credential Object for SRV1

$ASRV1    = 'SRV1\Administrator'
$PSRV1    = 'Pa$$w0rd'
$PSSRV1   = ConvertTo-SecureString -String $PSRV1 -AsPlainText -Force
$CredSRV1 = [pscredential]::New($ASRV1, $PSSRV1)

Creating a script block to join SRV1 to the Resklt domain

$SB = {
  $ARK    = 'Reskit\Administrator'
  $PRK    = 'Pa$$w0rd'
  $PSRK   = ConvertTo-SecureString -String $PRK -AsPlainText -Force
  $CredRK = [pscredential]::New($ARK, $PSRK)
  $DJHT = @{
    DomainName  = 'Reskit.Org'
    OUPath      = 'OU=IT,DC=Reskit,DC=Org'
    Credential  = $CredRK
    Restart     = $false
 }
    Add-Computer @DJHT
}

Testing to ensure SRV1 is online

Test-NetConnection -ComputerName SRV1
Test-NetConnection -ComputerName SRV1 -port 5985

Joining the computer to the domain

Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
Invoke-Command -ComputerName SRV1 -Credential $CredSRV1 -ScriptBlock $SB

Restarting SRV1

Restart-Computer -ComputerName SRV1 -Credential $CredSRV1 -Force

Viewing the resulting computer accounts for Reskit.Org

Get-ADComputer -Filter * -Properties DNSHostName |
  Format-Table

How it works...

In step 1, you use Get-ADComputer to check the computers that you have added to the Reskit domain, with output like this:

Figure 4.3 4: Viewing Computers in the Reskit domain

In step 2, you use the Get-ADComputer to get the computer accounts in the UK.Reskit.Org domain, with output like this:

Figure 4.35: Viewing Computers in the UK child domain

To demonstrate creating a compute account in the AD, in step 3, you create a new computer object, Wolf, for the Reskit domain. This step creates no console output.

In step 4, you create a credential object you use in the next step. Step 5 creates a script block you run on SRV1 to make the host a domain member. These two steps produce no console output.

In step 6, you use Test-NetConnection to check that SRV1 is online, and you can make a connection to the WinRM port, which produces output like this:

Figure 4.36: Testing the connection to SRV2

In step 7, you invoke the script block ($SB) on SRV1 to join that host to the Reskit domain. This step produces no output. In Step 8., you restart SRV1 with no console output. If you are running SRV1 as a VM and connecting to the server using tools such as VMconnect, you should see the VM reboot. After the reboot – log in as Reskit\Administrator.

In step 9, you use Get-ADComputer to check on the computers in the Reskit domain, which has output like this:

Figure 4.37: Viewing Reskit AD computer objects

There's more...

There are two broad ways of adding a Windows computer to an AD domain. The first is to log on to the computer to be added and join the domain using Add-Computer. You must have login with credentials that have the permissions needed to add a computer to a domain (i.e., the domain administrator). You also need the credentials that enable you to log on to the system itself. Alternatively, you can create a computer object in the AD in advance. This approach is known as pre-staging. You need administrator credentials to add the computer object to the domain, but once you have pre-staged the account, any user can join the computer to the domain.

In steps 1 and step 2, you use Get-ADComputer to get the computer accounts in the Reskit and the UK (child) domain. Note that in parent domains (Reskit), you have a computer account related to the child domain (UK), and in the child domain, you can see a computer account for the parent domain. These accounts are fundamental to the operation of a multi-domain forest – do NOT touch them unless you want to experiment within domain recovery!

In step 3, you pre-stage the Wolf computer. A user able to log on to Wolf could then use Add-Computer (or the GUI) to add the host to the domain. In step 4, you add a computer to a domain using domain administrator credentials.

Adding/Removing users using a CSV file

Adding users (or computers) is straightforward, as you saw in the two previous recipes. These methods are great for adding one or two users/computers, but if you have a larger number of users or computers to add, this can become long-winded.

Spiceworks (https://www.spiceworks.com/) is an excellent site for IT Professionals to learn more and get their problems solved. Spiceworks has a busy PowerShell support forum which you can access at https://community.spiceworks.com/programming/powershell. A frequently asked (and answered) question at Spiceworks is: How do I add multiple users using an input file? This recipe does just that.

You start by creating a CSV file containing details of the users you want to add. In production, you would add additional fields to the rows in the CSV and amend the recipe script to add these property values to each user you create.

This recipe uses a CSV file of users to add to AD, with a limited set of properties and values. In production, you would most likely extend the information contained in the CSV based on your business needs and the information you want to store in AD. You can extend this method to add computers to the AD and to create/populate AD groups.

Getting ready

You run this recipe on DC1, a domain controller in the Reskit domain on which you have installed PowerShell 7 and VS Code. Login to DC1 as Reskit\Administrator.You should also have DC1 and DC2 up and running.

This recipe creates and uses a CSV file. As an alternative to using step1 in the recipe, you can also download the CSV from this book’s GitHub repository at: https://github.com/doctordns/PacktPS72/blob/main/Scripts/Goodies/Users.Csv. If you download it from Github, make sure you store it in C:\Foo\Users.CSV.

How to do it...

Creating a CSV file

$CSVDATA = @'
FirstName,Initials,LastName,UserPrincipalName,Alias,Description,Password
P, D, Rowley, PDR, Peter, Data Team, Christmas42
C, F, Smith, CFS, Claire, Receptionist, Christmas42
Billy, Bob, JoeBob, BBJB, BillyBob, One of the Bobs, Christmas42
Malcolm, D, Duewrong, Malcolm, Malcolm, Mr. Danger, Christmas42
'@
$CSVDATA | Out-File -FilePath C:\Foo\Users.Csv

Importing and displaying the CSV

$Users = Import-CSV -Path C:\Foo\Users.Csv |
  Sort-Object -Property Alias
$Users | Format-Table

Adding the users using the CSV

$Users |
  ForEach-Object -Parallel {
    $User = $_
    #  Create a hash table of properties to set on created user
    $Prop = @{}
    #  Fill in values
    $Prop.GivenName         = $User.FirstName
    $Prop.Initials          = $User.Initials
    $Prop.Surname           = $User.LastName
    $Prop.UserPrincipalName = $User.UserPrincipalName + "@Reskit.Org"
    $Prop.Displayname       = $User.FirstName.Trim() + " " +
                              $User.LastName.Trim()
    $Prop.Description       = $User.Description
    $Prop.Name              = $User.Alias
    $PW = ConvertTo-SecureString -AsPlainText $User.Password -Force
    $Prop.AccountPassword   = $PW
    $Prop.ChangePasswordAtLogon = $true
    $Prop.Path                  = 'OU=IT,DC=Reskit,DC=ORG'
    $Prop.Enabled               = $true
    #  Now Create the User
    New-ADUser @Prop
    # Finally, Display User Created
    "Created $($Prop.Name)"
}

Showing all users in AD (Reskit.Org)

Get-ADUser -Filter * -Property Description |
  Format-Table -Property Name, UserPrincipalName, Description

How it works...

In step 1, which produces no output, you create a simple CSV file which you save to C:\Foo\Users.CSV.

In step 2, you import this newly created CSV file and display the information it contains, which looks like this:

Figure 4.38: Viewing CSV contents

In step 3, you add each user contained in the CSV into AD. You add the users using New-ADUser, which itself produces no output. This step also displays additional output to show the users you added, which looks like this:

Figure 4.39: Adding new users to the Reskit domain

In step 4, you view the AD users in the Reskit domain, with output like this:

Figure 4.40: Adding new users to the Reskit domain

There's more...

In step 3, you add users based on the CSV. You add these users explicitly to the IT OU, using the parameter -Path (as specified in the $Prop hash table). In production, when adding users that could reside in different OUs, you should extend the CSV to include the distinguished name of the OU into which you wish to add each user.

Creating Group Policy Objects

In Windows AD, Group policy allows you to define computer and user configuration settings that AD applies to each system/user. GPOs ensure a system remains configured per policy. Each time a domain-joined computer starts up or a domain user logs in, the local group policy agent on your computer obtains the group policy settings from AD and ensures they are applied. Additionally, the group policy client regularly re-applies policies at a regular interval.

In this recipe, you start by creating a group policy object within the Active Directory. You then configure the GPO, enabling computers in the IT organizational unit to use PowerShell scripts on those systems or set a specific screen saver.

You can configure thousands of settings for a user or computer through group policy. Microsoft has created a spreadsheet that lists the policy settings, which you can download from https://www.microsoft.com/en-us/download/101451. At the time of writing, the spreadsheet covers the Group Policy template files delivered with Windows 10 May 2020 Update (aka Windows 10 2004).

Once you create and configure your GPO object, you link the policy object to the OU you want to configure. You can apply a GPO to the domain, to a specific AD site, or to an OU (or child OU). You can also assign any GPO to multiple OUs, simplifying your OU design.

The configuration of a GPO typically results in Windows generating information that a host’s group policy agent (the code that applies the GPO objects) can access. This information tells the agent how to work. Settings made through administrative templates use registry settings inside Registry.POL files. The group policy agent obtains the policy details from the SYSVOL share on a domain controller and applies them whenever a user logs on or off or when a computer starts up or shuts down.

The group policy module also produces nice-looking reports describing the group policy objects on your system. There are default HTML reports that give you a quick look at the GPOs on your system. You can also get that same information as XML and create your own more customized reports.

Getting ready

You run this recipe on DC1, a domain controller in the Reskit.Org domain. You created this DC in “Installing an Active Directory forest root domain” and after creating and populating the IT OU.

How to do it...

Creating a new Group Policy object

$Pol = New-GPO -Name ITPolicy -Comment "IT GPO" -Domain Reskit.Org

Ensuring just computer settings are enabled

$Pol.GpoStatus = 'UserSettingsDisabled'

Configuring the policy with two registry-based settings

$EPHT1= @{
  Name   = 'ITPolicy'
  Key    = 'HKLM\Software\Policies\Microsoft\Windows\PowerShell'
  ValueName = 'ExecutionPolicy'
  Value  = 'Unrestricted'
  Type   = 'String'
}
Set-GPRegistryValue @EPHT1 | Out-Null
$EPHT2= @{
  Name   = 'ITPolicy'
  Key    = 'HKLM\Software\Policies\Microsoft\Windows\PowerShell'
  ValueName = 'EnableScripts'
  Type   = 'DWord'
  Value  = 1
}
Set-GPRegistryValue @EPHT2 | Out-Null

Creating a screen saver GPO

$Pol2 = New-GPO -Name 'Screen Saver Time Out'
$Pol2.GpoStatus   = 'ComputerSettingsDisabled'
$Pol2.Description = '15 minute timeout'

Setting a Group Policy enforced registry value

$EPHT3= @{
  Name   = 'Screen Saver Time Out'
  Key    = 'HKCU\Software\Policies\Microsoft\Windows\'+
              'Control Panel\Desktop'
  ValueName = 'ScreenSaveTimeOut'
  Value  = 900
  Type   = 'DWord'
}
Set-GPRegistryValue @EPHT3 | Out-Null

Linking both GPOs to the IT OU

$GPLHT1 = @{
  Name     = 'ITPolicy'
  Target   = 'OU=IT,DC=Reskit,DC=org'
}
New-GPLink @GPLHT1 | Out-Null
$GPLHT2 = @{
  Name     = 'Screen Saver Time Out'
  Target   = 'OU=IT,DC=Reskit,DC=org'
}
New-GPLink @GPLHT2 | Out-Null

Displaying the GPOs in the domain

Get-GPO -All -Domain Reskit.Org |
  Sort-Object -Property DisplayName |
    Format-Table -Property Displayname, Description, GpoStatus

Creating and viewing a GPO Report

$RPath = 'C:\Foo\GPOReport1.HTML'
Get-GPOReport -All -ReportType Html -Path $RPath
Invoke-Item -Path $RPath

Getting report in XML format

$RPath2 = 'C:\Foo\GPOReport2.XML'
Get-GPOReport -All -ReportType XML -Path $RPath2
$XML = [xml] (Get-Content -Path $RPath2)

Creating a simple GPO report

$RPath2 = 'C:\Foo\GPOReport2.XML'
$FMTS = "{0,-33}  {1,-30} {2,-10} {3}"
$FMTS -f 'Name','Linked To', 'Enabled', 'No Override'
$FMTS -f '----','---------', '-------', '-----------'
$XML.report.GPO |
  Sort-Object -Property Name |
    ForEach-Object {
     $Gname = $_.Name
     $SOM = $_.linksto.SomPath
     $ENA = $_.linksto.enabled
     $NOO = $_.linksto.nooverride
     $FMTS -f $Gname, $SOM, $ENA, $NOO
   }

How it works...

Like many AD-related cmdlets, the cmdlets you use to manage Group Policy objects do not produce much output.

In step 1, you create a new GPO in the Reskit.Org domain. This step creates an empty GPO in the domain that is not yet linked to any OU and thus does not apply to any computer or user.

In step 2, you disable user settings, which allows the GPO client to ignore any user settings. Doing so can make the client’s GPO processing a bit faster.

In step 3, you set this GPO to have two specific registry-based values. When a computer starts up, the GPO processing on that client computer ensures that these two registry values are set on the client. During Group Policy refresh (which happens approximately every 2 hours), the group policy agent enforces the value contained in the policy.

In step 4 and step 5, you create a new GPO and set a screen saver timeout of 900 seconds.

In step 6, you link the two GPOs to the IT Organizational unit. GPO processing ignores the GPO until you link the GPOs to an OU (or to the domain or a domain site).

In this recipe, step 1 through step 6 produces no output.

In step 7, you use Get-GPO to get and display the details of the GPOs in the domain, which looks like this:

Figure 4.41: Adding new users to the Reskit domain

In step 8, you use the Get-GPOReport cmdlet to generate an HTML report on policy objects in the domain. This step displays the results: creates a nicely formatted report, which looks like this:

Figure 4.42: Viewing a GPO report

In step 9, you use the Get-GPOReport cmdlet to get information about the GPOs on your system and return this information as XML. This step produces no console output.

Then, in step 10, you use PowerShell to produce a nicely formatted report customized for your needs which looks like this:

Figure 4.43: Creating a customized GPO report

There's more...

In step 8, you see the output from the Get-GPOReport cmdlet from Internet Explorer. At the time of writing, the Edge browser does not render the output as nicely as you might want. What may look like a bug in the graphic is just how Edge (again at the time of writing) renders this HTML document.

In step 9, you use the Get-GPOReport command to obtain a report of all GPOs in the domain returned as XML. The XML returned contains details of the GPO links as a property which includes many sub-properties. To create nice looking output, you create a format string you use to display each row of your report, including report header lines. You first use this format string to make the two report header lines for the report. In practice, you might need to adjust the composite formatting strings ($FMTS) to ensure the widths of each column are appropriate for your environment

In step 10, you report on the GPOs in the Reskit Domain using .NET composite string formatting using the -f operator. Using .NET composite formatting enables you to create nice-looking customized output when the objects returned by a cmdlet are not in a form to be used directly with Format-Table.

The Format-Table and Format-List cmdlets are of most use when each object has simple properties. When an object has properties that are objects which also have properties, the format commands do not surface these (sub) properties. In this case, you must obtain the details manually of the GPO links for each GPO and generate your report lines based on those results. While the report layout works well for this specific set of GPOs, - should you have GPOs with longer names, or linked to deeper organizational units, you may need to adjust the format string you set in Step 10.

Reporting on AD Users

Monitoring the Active Directory is a necessary albeit time-consuming task. With larger numbers of users and computers to manage, you need all the help you can get, and PowerShell makes it easy to keep track of things.

If a user has not logged on for a reasonable period, the account could be a security risk. Likewise, a user with membership in a privileged account (for example, Enterprise Admins) could be used by an attacker. IT professionals know how easy it is to put someone in a high privilege group rather than set up more fine-grained permissions using something like Just Enough Administration (see “Implementing JEA” in Chapter 8).

Regular reporting can help focus on accounts that could be usefully de-activated, removed from a security group, or possibly removed altogether.

In this recipe, you obtain all the accounts in the AD and examine potential security risks.

Getting ready

After running the recipes in this chapter, you run this recipe on DC1, a DC in the Reskit.Org domain. You should also have installed PowerShell 7 and VS Code on this host.

How to do it...

Defining a function Get-ReskitUser to return objects related to users in the Reskit.Org domain

Function Get-ReskitUser {
# Get PDC Emulator DC
$PrimaryDC = Get-ADDomainController -Discover -Service PrimaryDC
# Get Users
$ADUsers = Get-ADUser -Filter * -Properties * -Server $PrimaryDC
# Iterate through them and create $Userinfo hash table:
Foreach ($ADUser in $ADUsers) {
    # Create a userinfo HT
    $UserInfo = [Ordered] @{}
    $UserInfo.SamAccountname = $ADUser.SamAccountName
    $Userinfo.DisplayName    = $ADUser.DisplayName
    $UserInfo.Office         = $ADUser.Office
    $Userinfo.Enabled        = $ADUser.Enabled
    $userinfo.LastLogonDate  = $ADUser.LastLogonDate
    $UserInfo.ProfilePath    = $ADUser.ProfilePath
    $Userinfo.ScriptPath     = $ADUser.ScriptPath
    $UserInfo.BadPWDCount    = $ADUser.badPwdCount
    New-Object -TypeName PSObject -Property $UserInfo
    }
} # end of function

Getting the users

$RKUsers = Get-ReskitUser

Building the report header

$RKReport = ''  # first line of the report
$RkReport += "*** Reskit.Org AD Report`n"
$RKReport += "*** Generated [$(Get-Date)]`n"
$RKReport += "*******************************`n`n"

Reporting on disabled users

$RkReport += "*** Disabled Users`n"
$RKReport += $RKUsers |
    Where-Object {$_.Enabled -NE $true} |
        Format-Table -Property SamAccountName, Displayname |
            Out-String

Reporting on users who have not recently logged on

$OneWeekAgo = (Get-Date).AddDays(-7)
$RKReport += "`n*** Users Not logged in since $OneWeekAgo`n"
$RkReport += $RKUsers |
    Where-Object {$_.Enabled -and $_.LastLogonDate -le $OneWeekAgo} |
        Sort-Object -Property LastlogonDate |
            Format-Table -Property SamAccountName,lastlogondate |
                Out-String

Discovering users with a high number of invalid password attempts

$RKReport += "`n*** High Number of Bad Password Attempts`n"
$RKReport += $RKUsers | Where-Object BadPwdCount -ge 5 |
  Format-Table -Property SamAccountName, BadPwdCount |
    Out-String

Adding another report header line for this part of the report and creating an empty array of privileged users

$RKReport += "`n*** Privileged  User Report`n"
$PUsers = @()

Querying the Enterprise Admins/Domain Admins/Scheme Admins groups for members and adding them to the $Pusers array

# Get Enterprise Admins group members
$Members = Get-ADGroupMember -Identity 'Enterprise Admins' -Recursive |
    Sort-Object -Property Name
$PUsers += foreach ($Member in $Members) {
    Get-ADUser -Identity $Member.SID -Properties * |
        Select-Object -Property Name,
               @{Name='Group';expression={'Enterprise Admins'}},
               whenCreated,LastLogonDate
}
# Get Domain Admins group members
$Members =
  Get-ADGroupMember -Identity 'Domain Admins' -Recursive |
    Sort-Object -Property Name
$PUsers += Foreach ($Member in $Members)
    {Get-ADUser -Identity $member.SID -Properties * |
        Select-Object -Property Name,
                @{Name='Group';expression={'Domain Admins'}},
                WhenCreated, Lastlogondate,SamAccountName
}
# Get Schema Admins members
$Members =
  Get-ADGroupMember -Identity 'Schema Admins' -Recursive |
    Sort-Object Name
$PUsers += Foreach ($Member in $Members) {
    Get-ADUser -Identity $member.SID -Properties * |
        Select-Object -Property Name,
            @{Name='Group';expression={'Schema Admins'}},
            WhenCreated, LastLogonDate,SamAccountName
}

Adding the special users to the report

$RKReport += $PUsers | Out-String

Displaying the final report

$RKReport

How it works...

All the steps except the last one produce no output in this recipe. The first steps create a report which you view in the final step.

In step 1, you create a function, Get-ReskitUser, which creates a set of user objects related to each user in your AD. In step 2, you use the function to populate an array, $RKUsers, containing users and necessary details needed for your report.

In step 3, you build the header for the report, and then in step 4, you build a report section on disabled users, adding details of disabled user accounts. In step 5, you add details of users that have not logged on within the last seven days. In step 6, you add details of users who have had more than five unsuccessful login attempts.

The final section of the report lists members of crucial AD groups. With step 7, you create a header for this section. Then with step 8 and step 9, you add details of members of these groups.

Once these steps are complete, in step 10, you can view the output of the report, which looks like this:

Figure 4.44: Viewing the final Users report

There's more...

In step 1, you create a function to retrieve users. This function allows you to reformat the properties as needed, improving the output in your report.

In steps 3 through step 9, you build the report contents by adding to a variable holding the report. These steps show you how to create highly customized reports for your domain.

In step 10, you see the final report. Note that the users Malcolm and ThomasL have a high number of failed login attempts but logged in at some point successfully. If you log in using a domain account and an incorrect password, AD rejects the login and increases the bad attempt count. However, the AD zeros the bad logon count once you log in successfully. You might also ask why both those users are members of the Enterprise Admins group!

Reporting on AD Computers

Monitoring AD user accounts is an important task, and as you saw in “Reporting on AD Users” you can automate monitoring with PowerShell. Monitoring computer accounts can also be useful. And with larger numbers of computers to manage, you need all the help you can get.

A computer that has not logged on for an extended period could represent a security risk or could be a lost/stolen computer. It could also be a system you have not rebooted after applying patches and updates.

This recipe creates a report of computers that have not logged on or that you have not rebooted for a while.

One challenge in developing scripts like this is creating meaningful test data. If you wish to generate a test report showing a system that has not logged in for over 6 months, you might have to wait for 6 months to get the necessary data. This recipe offers a way around that, if only for testing purposes.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7.

How to do it...

Creating example computer accounts in the AD

$NCHT1 = @{
    Name        = 'NLIComputer1_1week'
    Description = 'Computer last logged in 1 week ago'
}
New-ADComputer @NCHT1
$NCHT2 = @{
  Name        = 'NLIComputer2_1month'
  Description = 'Computer last logged in 1 week ago'
}
New-ADComputer @NCHT2
$NCHT3 = @{
  Name        = 'NLIComputer3_6month'
  Description = 'Computer last logged in 1 week ago'
}
New-ADComputer @NCHT3

Creating some constants for later comparison

$OneWeekAgo   = (Get-Date).AddDays(-7)
$OneMonthAgo  = (Get-Date).AddMonths(-1)
$SixMonthsAgo = (Get-Date).AddMonths(-6)

Defining a function to create sample data

Function Get-RKComputers {
$ADComputers = Get-ADComputer -Filter * -Properties LastLogonDate
$Computers = @()
foreach ($ADComputer in $ADComputers) {
  $Name = $ADComputer.Name
  # Real computers and last logon date
  if ($adComputer.name -NotMatch "^NLI") {
    $LLD = $ADComputer.LastLogonDate
  }
  Elseif ($ADComputer.Name -eq "NLIComputer1_1week")  {
    $LLD = $OneWeekAgo.AddMinutes(-30)
  }
  Elseif ($ADComputer.Name -eq "NLIComputer2_1month")  {
    $LLD = $OneMonthAgo.AddMinutes(-30)
  }
  Elseif ($ADComputer.Name -eq "NLIComputer3_6month")  {
    $LLD = $SixMonthsAgo.AddMinutes(-30)
  }
  $Computers += [pscustomobject] @{
    Name = $Name
    LastLogonDate = $LLD
  }
}
$Computers
}

Building the report header

$RKReport = ''           # Start of report
$RKReport += "*** Reskit.Org AD Daily AD Computer Report`n"
$RKReport += "*** Generated [$(Get-Date)]`n"
$RKReport += "***********************************`n`n"

Getting Computers in RK AD using Get-RKComputers

$Computers = Get-RKComputers

Getting computers that have never logged on

$RKReport += "Computers that have never logged on`n"
$RkReport += "Name                    LastLogonDate`n"
$RkReport += "----                    -------------`n"
$RKReport += Foreach($Computer in $Computers) {
  If ($null -eq $Computer.LastLogonDate) {
   "{0,-22}  {1}  `n" -f $Computer.Name, "Never"
  }
}

Reporting on computers who have not logged on in over 6 months

$RKReport += "`nComputers that have not logged in over 6 months`n"
$RkReport += "Name                    LastLogonDate`n"
$RkReport += "----                    -------------`n"
$RKReport +=
foreach($Computer in $Computers) {
  If (($Computer.LastLogonDate -lt $SixMonthsAgo) -and
      ($null -ne $Computer.LastLogonDate)) {
("`n{0,-23}  {1}  `n" -f $Computer.Name, $Computer.LastLogonDate)
  }
}

Reporting on computer accounts that have not logged in 1-6 months ago

$RKReport += "`n`nComputers that have not logged in 1-6 months`n"
$RkReport += "Name                    LastLogonDate`n"
$RkReport += "----                    -------------"
$RKReport +=
foreach($Computer in $Computers) {
  If (($Computer.LastLogonDate -ge $SixMonthsAgo) -and
     ($Computer.LastLogonDate -lt $OneMonthAgo) -and
       ($null -ne $Computer.LastLogonDate)) {
   "`n{0,-22}  {1}  " -f $Computer.Name, $Computer.LastLogonDate
  }
}

Reporting on computer accounts that have not logged in the past 1 week to one month ago

$RKReport += "`n`nComputers that have between one week "
$RKReport += "and one month ago`n"
$RkReport += "Name                    LastLogonDate`n"
$RkReport += "----                    -------------"
$RKReport +=
foreach($Computer in $Computers) {
  If (($Computer.LastLogonDate -ge $OneMonthAgo) -and
     ($Computer.LastLogonDate -lt $OneWeekAgo) -and
       ($null -ne $Computer.LastLogonDate)) {
   "`n{0,-22}  {1}  " -f $Computer.Name, $Computer.LastLogonDate
  }
}

Displaying the report

$RKReport

How it works...

In this recipe, all but the final step produce no output. Some of the steps exist to enable you to test the report that this recipe generates. Some of these steps might not be necessary for real life, as you already have enough real-life data to create a complete report.

In step 1, you create three AD computer accounts. You use these accounts to simulate computers that have not logged on for a while, thus enabling you to view a complete report. If you re-run this recipe or this step, adding these accounts produces errors since the accounts already exist. You could modify this step to check to see if the accounts exist before creating them.

In step 2, you create three time constants, representing the time 7 days ago, one month ago, and six months ago. This step lets you test if a given user account has not longed on in that period.

In step 3, you create a new function, Get-RKComputers. This function returns a list of all the computer accounts in AD along with their last logon time.

In step 4, you begin the report by creating a report header.

In step 5, you call the Get-RKComputers function and populate the $Computers array (all the computers available).

In step 6 through step 9, you add details to the report of computers who have never logged on, have not logged on for over 6 months, have not logged on in 1-6 months, and computers that have not logged in 1 week-1 month.

In the final step, step 10, you display the report created by the earlier steps. This output of this step looks like this:

Figure 4.45: Viewing the final Computer report

There's more...

In step 3, you create a function to get the computer accounts in AD. This function returns an array of computer names and last login date and includes values for the three test computer accounts’ previous logon date. In production, you might amend this function to return computer accounts from just certain OUs. You could also extend this function to test whether each computer is online by testing a network connection or checking to see if there is a DNS A record for the computer to detect stale computer accounts.

In step 5 through step 9, you create a report by adding text lines to the $RKReport variable. You need to add CRLF characters before or after each line of text when you add each line to the report. Ensuring each report line begins in the right place can be challenging in creating reports using the technique shown by this recipe.

Managing AD Replication

Active Directory uses a special database to support its operations. The database is a distributed, multi-master database with convergence—every DC in every domain stores this database in the file C:\Windows\NTDS\ntds.dit.

Every DC in any domain holds a complete copy of this database. If you add a new user or change a user’s office, that change occurs on just one DC (initially). AD replication makes the change in all database copies. In this way, the database remains consistent over time and across all DCs.

AD replication is based on partitions - a slice of the overall database. AD can replicate each partition separately. There are several partitions in AD:

Schema partition holds the AD schema that defines each object that AD stores in the database. The schema also defines the properties of all these objects.

Configuration partition - this holds the details of the structure of the domain.

Domain partition - this partition, also known as the domain naming context, contains the objects relating to a domain (users, groups, OUs, etc.). The objects in this partition are defined based on the schema.

Application partition - some applications, such as DNS, store objects in your AD and rely on AD replication to replicate the values.

There are two types of replication: intra-site replication and inter-site replication. Intra-site replication happens between DCs in a given AD site, while inter-site replication occurs between different AD sites.

You can create different topologies for replication, including:

Ring - each DC in a site has at least two inbound replication partners. When any change is made to any DC, that DC notifies its replication partners that it has a change. Those DCs can then replicate that change (if they have not seen it before). AD by default ensures there are not more than three hops within the replication topology. If you have a large number of DCs (more than 7), AD automatically creates additional replication links to keep the hop count below 3.

Full mesh - with this topology, all DCs replicate to all other DCs. Full mesh replication keeps the database in sync with a minimum replication delay, but can be more expensive in terms of bandwidth (and DC utilization). It is not scalable.

Hub and spoke - You might use this approach in enormous organizations where a ‘‘spoke’’ DC replicates with a central hub DC. The hub DC then replicates the change to all other spoke DCs in your organization. Hub and spoke can reduce replication for widely dispersed implementations.

Hybrid - here you can combine any of these based on business needs.

By default, AD replication uses a ring topology. If your business needs dictate, you can adopt different topologies requiring additional configuration.

In most smaller organizations (such as Reskit.Org), replication is set up and operates automagically. But for large and distributed organizations, replication can be quite complicated as you attempt to balance being totally up-to-date against the cost of geographical bandwidth. If you have DCs on several continents, you want to collect those changes and do them simultaneously, say every 4 hours. But that means the remote DC would have out-of-date information for a period. As a general rule of thumb, you should design replication to happen faster than a person can fly between your AD sites served by a DC. If you change your password, say in London, then as long as the changes occur within 16 hours when you fly to Brisbane, Australia, the Australian DCs contain the replicated password. Thus, you can log in with the new password immediately upon landing in Brisbane.

For more information on AD replication concepts, see https://docs.microsoft.com/windows-server/identity/ad-ds/get-started/replication/active-directory-replication-concepts.

For any sizeable organization, the design and planning of AD are vital - see https://docs.microsoft.com/windows-server/identity/ad-ds/plan/ad-ds-design-and-planning for more information on the planning and design work necessary.

Traditionally you use many Win32 console applications to manage and troubleshoot replication, including repadmin.exe, which broadly replaces an earlier command replmon.exe. See the following for details on the repadmin.exe command (and replication) https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/getting-over-replmon/ba-p/396687.

With the advent of PowerShell and the PowerShell active directory module, you can now perform many of the functions of repadmin.exe using PowerShell cmdlets. In this recipe, you examine some of the details of AD replication using PowerShell.

In this recipe, you examine replication details and see it in action.

Getting Ready

You run this recipe on DC1, a domain controller in the Reskit.Org domain. You should also have DC2 and UKDC1 available and online at the start of testing this recipe. You must have installed PowerShell 7 and VS Code on all these hosts.

How to do it…

Checking replication partners for DC1

Get-ADReplicationPartnerMetadata -Target DC1.Reskit.Org   |
  Format-List -Property Server, PartnerType, Partner,
                        Partition, LastRep*

Checking AD replication partner metadata in the domain

Get-ADReplicationPartnerMetadata -Target Reskit.Org -Scope Domain |
  Format-Table -Property Server, P*Type, Last*

Investigating group membership metadata

$REPLHT = @{
  Object              = (Get-ADGroup -Identity 'IT Team')
  Attribute           = 'Member'
  ShowAllLinkedValues = $true
  Server              = (Get-ADDomainController)
}
Get-ADReplicationAttributeMetadata @REPLHT |
  Format-Table -Property A*NAME, A*VALUE, *TIME

Adding two users to the group and removing one

Add-ADGroupMember -Identity 'IT Team' -members Malcolm
Add-ADGroupMember -Identity 'IT Team' -members Claire
Remove-ADGroupMember -Identity 'IT Team' -members Claire -Confirm:$False

Checking updated metadata

# From DC1
Get-ADReplicationAttributeMetadata @REPLHT |
  Format-Table -Property A*NAME,A*VALUE, *TIME
# From DC2
Get-ADReplicationAttributeMetadata -Server DC2 @REPLHT |
  Format-Table -Property A*NAME,A*VALUE, *TIME

Make a change to a user

$User = 'Malcolm'
Get-ADUser -Identity $User  |
  Set-ADUser -Office 'Marin Office'

Checking updated metadata

$O = Get-ADUser -Identity $User
# From DC1
Get-ADReplicationAttributeMetadata -Object $O -Server DC1 |
  Where-Object AttributeName -match 'Office'
# From DC2
Get-ADReplicationAttributeMetadata -Object $O -Server DC2 |
  Where-Object AttributeName -match 'Office'

Examine Replication partners for both DC1, UKDC1

Get-ADReplicationConnection |
  Format-List -Property Name,ReplicateFromDirectoryServer
Get-ADReplicationConnection -Server UKDC1 |
  Format-List -Property Name,ReplicateFromDirectoryServer

Use repadmin to check the replication summary

repadmin /replsummary

How it works…

In step 1, you check to discover replication partners for DC1. With only two DCs in the Reskit domain (DC1 and DC2), the output of this step looks like this:

Figure 4.46: Viewing DC1 replication partners (For Reskit.Org domain)

In step 2, you get the AD replication partner metadata in the Reskit domain, with output like this:

Figure 4.47: Viewing DC1 replication metadata

In step 3, you view the AD replication metadata for the group membership for a group (IT Team), with output like this:

Figure 4.48: Viewing Group Membership metadata

In step 4, you add two users to a group, then remove one, creating no console output. In step 5, you look again at the group metadata, which reflects the change to group status, as you can see here:

Figure 4.49: Viewing Group Membership metadata after group changes

In step 6, you update the details of a given user, generating no output. In step 7, you check the replication metadata for this user to see what was changed. You view the replication data from both DC1 and DC2 with an output that looks like this:

Figure 4.50: Viewing User change details

In step 8, you view the details of replication partners for both DC1 and the child DC, UKDC1, with output like this:

Figure 4.51: Viewing replication partners for DC1, UKDC1

In the final step in this recipe, step 9, you use the repadmin console application to view an overview of replication in the forest, with output like this:

Figure 4.52: Viewing replication status for the Reskit forest

There’s more…

In step 1, you view the replication partner(s) for the Reskit domain. With just two DCs in the Reskit domain, you would expect DC1 and DC2 to be replication partners for each other. And in step 2, you can see that replication for the domain is up to date,

In step 3, you view the metadata related to the group membership of the IT Team group. So far, you can see that AD records three changes to the membership of this group (which happened when you added these three users to the group in an earlier recipe). In step 4, you add two users to the group and remove one. In step 5, you again view the metadata to see you (or someone!) added Malcolm and Claire to the group and immediately removed Claire.

As another example of replication, in step 6 you change a user’s Office property, then in step 7, you examine the replication metadata. You can see that someone made a change to this user, changing their office name.

In step 9, you use the repadmin tool and verify that there are currently no replication issues in the forest.