Skip to main content

· 2 min read

Sometimes I get a script idea put in my head that's so irritatingly pervasive that the only fix is to write the damned script. David Szpunar from the NinjaOne Users Discord made a somewhat passing comment about time drift causing issues with a remote support tool and that let to me thinking... You could probably monitor for that with a PowerShell one-liner right?

Wrong! Turns out that it's more than one line!

The Script

This Script Requires Input
This script requires user input, whether in the form of variables, parameters or edits to the script itself before you can run it. Areas where you need to provide input will be indicated with:
### Inline Comments
and / or
'<MARKED STRINGS>'
Parameters will be indicated before the script block.
This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/17.

Test-TimeDrift.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/TimeDrift.ps1

Using The Script

We tested this as a "Script Result Condition" in NinjaOne set to trigger the monitor if a machine's time drifts by more than 10 seconds from uk.pool.ntp.org (the UK's NTP pool) and it worked like a charm. The script is pretty self-explanatory but here's a quick rundown of what it does:

  1. It uses a configurable NTP or SNTP server to get the "reference" time. (Parameter -ReferenceServer)
  2. It uses the w32tm executable to conduct a number of skew checks against that reference server (Parameter -NumberOfSamples)
  3. It averages the samples and compares the result to the threshold (Parameter -AllowedTimeDrift)
  4. Optionally you can force a resync if the time drift is greater than the threshold (Parameter -ForceResync)

If the average time drift is greater than the threshold, the script returns a non-zero exit code and the monitor triggers. If the w32tm command errors (non existent server, network down etc) the script returns a non-zero exit code and the monitor triggers.

Credits

This script borrows ideas and the approach and a little code from the excellent blog of Kevin Holman.

The formidable Chris Taylor helped with a cool suggestion to suppress empty lines in the output and his site is well worth a visit.

· 7 min read

This post will hold detection scripts for any serious CVE vulnerability that we write detection scripts for in the future. It will be updated and added to as new vulnerability detection scripts are written.

CVE-2022-41099

This script has been compiled using information from the following Microsoft sources:

security

This article relates to CVE-2022-41099 which is a vulnerability in the Windows Recovery Environment (WinRE) which could allow a successful attacker to bypass the BitLocker Device Encryption feature on the system storage device. An attacker with physical access to the target could exploit this vulnerability to gain access to encrypted data.

Fixed a Bug

Thanks to DTGBilly from the NinjaOne Users Discord for pointing out that in altogether far too many places I had typo'd the CVE as CVE-4022-41099 instead of CVE-2022-41099 🤦‍♂️ this included field names and labels so please check yours are correct as now shown in the post.

Parameters!

Since version 1.2.0 (2023-03-21) this script now requires one of two mandatory parameters.

  • If you are checking for the presence of the small "Safe OS Dynamic Update (SODU)" which is the minimum required change to mitigate the vulnerability use the -CheckPackage parameter and if required alter the -MountDirectory and -LogDirectory parameters (defaults to C:\RMM\WinRE).

  • If you are checking for the presence of the larger "Servicing Stack Update (SSU)" or "Dynamic Cumulative Update" which updates more than is required to mitigate the vulnerability, but may offer other benefits including new WinRE functionality or more reliable reset/restore behaviours use the -CheckImage parameter which checks the image build version.

If you were passing these in NinjaOne your parameter preset might look like this:

-CheckPackage -MountDirectory C:\RMM\WinRE -LogDirectory C:\RMM\WinRE

or this:

-CheckImage

Windows Recovery Environment (WinRE) Not Enabled

Before version 1.3.0 the script did not check if WinRE was enabled which could lead to confusing error output in the event WinRE was disabled. Now if you get the WinRE not enabled warning you are clear on why the script isn't executing.

A simple reagentc /enable should enable WinRE or at least provide some useful troubleshooting output.

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding one role custom field for devices with the Windows Desktop or Laptop and Windows Server roles, note that we've customised slightly the autogenerated machine name here, if you use the default adjust the field name in the script appropriately.

Field LabelField NameField TypeDescription
CVE-2022-41099CVE202241099CheckboxWhether the device has a WinRE image vulnerable to CVE-2022-41099

The Script

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/04/13.

Detect-CVE202241099.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/CVE%20Detection/CVE-2022-41099.ps1

The Results

CVE-2022-41099 Related Custom Fields

We run this script daily and have a corresponding monitor setup to check CVE fields with a value of "Yes" and alert us if any are found. You'll find information on remediating this vulnerability in this followup post.

CVE-2023-23397

This script has been compiled using information from the following Microsoft sources:

Thanks to:
  • Concentus on the NinjaOne Users Discord for helping me run down and test different versions of Office to ensure this script was as accurate as possible.
  • Wisecompany on the One Man Band MSP Discord for reminding me to add an exit code and not overuse Write-Warning!
  • Thanks to KennyW on the MSPGeek Discord for helping find an error where certain versions were incorrectly detected as not vulnerable!
  • Thanks to Alkerayn on the NinjaOne Users Discord for helping find an error where certain channels were incorrectly detected as not vulnerable and identifying that we needed to first check the GPO-configured update channel!
  • Thanks to Tanner - MO on the MSPs R Us Discord for pointing out that version comparisons should all use -lt instead of -ne to ensure future compatibility / accuracy.
  • Thanks to DarrenWhite99 on the MSPGeek Discord for pointing out that the check for the GPO UpdateChannel was completely nonsensical and incompletely written.
  • Thanks to JSanz on the NinjaOne Users Discord for pointing out the GUID matching issue/bug.
  • Thanks to Jhn - TS on the NinjaOne Users Discord for discovering the issue with empty registry props causing the script to error.

This has only been tested against M365 Apps and Office 2021 VL versions "en masse" and only 64-bit office - if it doesn't work for you let me know on the NinjaOne Users Discord and I'll see what I can do to fix it!

security

This article relates to CVE-2023-23397 which is a vulnerability in Microsoft Outlook whereby an attacker could access a user's Net-NTLMv2 hash which could be used as a basis of an NTLM Relay attack against another service to authenticate as the user.

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding one role custom field for devices with the Windows Desktop or Laptop role, note that we've customised slightly the autogenerated machine name here, if you use the default adjust the field name in the script appropriately.

Field LabelField NameField TypeDescription
CVE-2023-23397CVE202323397CheckboxWhether the device has an Office or Microsoft 365 Apps version vulnerable to CVE-2023-23397.

The Script

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/04/13.

Detect-CVE202323397.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/CVE%20Detection/CVE-2023-23397.ps1

The Results

CVE-2023-23397 Related Custom Fields

We run this script daily and have a corresponding monitor setup to check CVE fields with a value of "Yes" and alert us if any are found. To remediate this vulnerability update Microsoft Office by running something like this:

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/16.

Update-C2ROffice.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Update%20Management/UpdateC2ROffice.ps1

This update script will force restart Office apps - it should restore open files automatically but if you want a softer approach replace the Start-Process line with:

Start-Process -FilePath $C2RPath -ArgumentList '/update user forceappshutdown=true updatepromptuser=true' -Wait

Prejay on the MSPGeek Discord has helpfully suggested the following to update C2R Office builds without a user logged in or as system:

Start-Process -FilePath $C2RPath -ArgumentList '/frequentupdate SCHEDULEDTASK displaylevel=false' -Wait

Mark Hodges (also on the MSPGeek Discord) has also helpfully suggested this more comprehensive update script which will update Office 2016 and 2019 as well as C2R Office.

CVE-2023-21554

This script has been compiled using information from the following Microsoft sources:

security

This article relates to CVE-2023-21554 which is a vulnerability in the Microsoft Message Queuing system which could allow remote code execution.

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding one role custom field for devices with the Windows Desktop or Laptop role, note that we've customised slightly the autogenerated machine name here, if you use the default adjust the field name in the script appropriately.

Field LabelField NameField TypeDescription
CVE-2023-21554CVE202321554CheckboxWhether the device has the MSMQ features installed and is missing the April 2023 Security Update.

The Script

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/16.

Detect-CVE202321554.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/CVE%20Detection/CVE-2023-21554.ps1

The Results

CVE-2023-21554 Related Custom Fields

We run this script daily and have a corresponding monitor setup to check CVE fields with a value of "Yes" and alert us if any are found. To remediate install the April 2023 Security Update.

· One min read

This post will show you how to use registry keys to test, set and remove target versions for Windows Feature Updates. This allows you to prevent Windows 10 or 11 from updating past your configured limit.

The Script

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/04/05.

Invoke-WindowsUpdateTargetVersion.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Update%20Management/FeatureUpdateTargeting.ps1
Parameters

When you run this script you might want to pass some parameters - here's what they do:

  • -Test - This will test the current target version settings and show you the results.
  • -Unset - This will remove the target version settings.
  • -TargetProductVersion - Specify the target version to aim for, examples would be 21H2 or 22H2.
  • -TargetProduct - Specify the target product to aim for, examples would be Windows 10 or Windows 11.

· One min read

This post will show you how to deploy the Printix client using NinjaOne Documentation fields and a PowerShell script.

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding two documentation fields to facilitate this script. You'll need to note your document template id, in the screenshots / our internal use we have a template called "Integration Identifiers" which we use to store any integration identifiers we need to reference in our scripts.

Field LabelField NameField TypeDescription
Printix Tenant IdprintixTenantIdTextHolds the customer's Printix tenant id.
Printix Tenant DomainprintixTenantDomainTextHolds the customer's Printix domain.

The Script

Install-PrintixClient.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Software%20Deployment/NinjaOne/PrintixClient.ps1
Parameters

When you run this script you need to pass your document template id. For example, sticking with our example above, you'd run the script with the parameter: -DocumentTemplate Integration Identifiers

The Results

Printix Documentation Fields

Printix Installation Activity

We run this script on a group of devices which don't have the Printix client installed.

· 5 min read

Collaboration with Martin Himken

This post and the WinRE patching script on Martin's blog at https://manima.de are the result of a collaboration between Martin and I to help mutually improve our various efforts towards patching CVE-2022-41099.

security

This article relates to CVE-2022-41099 which is a vulnerability in the Windows Recovery Environment (WinRE) which could allow a successful attacker to bypass the BitLocker Device Encryption feature on the system storage device. An attacker with physical access to the target could exploit this vulnerability to gain access to encrypted data.

If you're running Windows 10 or 11 you might have come across CVE-2022-41099 which is a vulnerability in the Windows Recovery Environment (WinRE) which could allow a successful attacker to bypass BitLocker if they can boot the device to WinRE. This is a pretty serious vulnerability and Microsoft have released a patch for it. However, the patch is not applied automatically and you need to take action to apply it.

Martin Himken has written a script to patch the WinRE drivers and I've written a script to download and stage the patch and servicing stack update files. The link to Martin's blog is at the top of this post and will be repeated at the end.

This script takes a few parameters to control it's behaviour. Parameter documentation follows:

ParameterTypeDescription
PatchFolderDirectoryInfoThe folder to download the patch files to. If not specified, C:\RMM\CVEs\2022-41099\ will be used.
AllSwitchIf specified, the script will download the patch files for all supported versions and available architechtures of Windows 10 and 11. If not specified, the script will only download the patch files for the version of Windows that is running on the device.
-All

Using the -All parameter will download a lot of files and take a long time to complete. It is recommended that you only use this parameter if you are patching a large number of devices or want to prepare a cache to serve files from.

Downloading all files consumes roughly 4.9GB of disk space.

The Script

info

This new version of the script downloads the Safe OS Dynamic Update (SODU) files - these are tiny and designed only to patch the vulnerable components.

Safe OS Dynamic Update (SODU) version.
This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/22.

Get-CVE202241099Patches.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Update%20Management/CVE202241099DownloaderSODU.ps1
Change Logs

Version: 1.5

Fixes incorrectly switched URLs for 19042 to 19045 for the x86 and x64 downloads. Thanks to Wisecompany for helping find this.

Version: 1.4

Update to use the Safe OS Dynamic Update packages which are considerably smaller.

Version: 1.3

Use $ProgressPreference to speed up execution. Thanks to https://github.com/CodyRWhite for the suggestion.

Version: 1.2

Fix a bug on line 82 where a hashtable of architectures was attempted to be accessed using the Windows build number. Thanks to Sir Loin of House WinAdmins for spotting this. (Yes, it's a Game of Thrones reference. So original.)

Version: 1.1

Adds handling for 19044.

Version: 1.0

Initial release.

info

This version of the script downloads the SSU and Dynamic Cumulative Update files - these are large and designed to update WinRE completely not just patch the vulnerability.

Servicing Stack Update (SSU) and Dynamic Cumulative Update (DCU) version.
Large Files

This script downloads the SSU and Dynamic Cumulative Update files - these are large and designed to update WinRE completely not just patch the vulnerability. This will require a lot of space both to download them (especially if using -All) and to apply them to WinRE.

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/22.

Get-CVE202241099Patches.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Update%20Management/CVE202241099Downloader.ps1
Change Logs

Version: 1.4

Empty the patch folder if it's not empty. Thanks to Wisecompany for the suggestion.

Version: 1.3

Use $ProgressPreference to speed up execution. Thanks to https://github.com/CodyRWhite for the suggestion.

Version: 1.2

Fix a bug on line 82 where a hashtable of architectures was attempted to be accessed using the Windows build number. Thanks to Sir Loin of House WinAdmins for spotting this. (Yes, it's a Game of Thrones reference. So original.)

Version: 1.1

Adds handling for 19044.

Version: 1.0

Initial release.

Examples

This example will download the applicable patch and SSU (if applicable) for CVE-2022-41099 to the folder C:\RMM\CVEs\2022-41099.

Get-CVE202241099Patches.ps1 -PatchFolder 'C:\RMM\CVEs\2022-41099\'

This example will download all patches for CVE-2022-41099 to the folder C:\RMM\CVEs\2022-41099. With subfolders for each KB and architecture.

Get-CVE202241099Patches.ps1 -PatchFolder 'C:\RMM\CVEs\2022-41099\' -All

Validating the Fix

By popular request you can validate the fix using the principles in the script used for CVE detection.

Collaboration with Martin Himken

This post and the WinRE patching script on Martin's blog at https://manima.de are the result of a collaboration between Martin and I to help mutually improve our various efforts towards patching CVE-2022-41099.

· 8 min read

This post uses code in part from the SMSAgent Blog.

This post takes a snippet from the SMSAgent Blog and adds some additional magic along with two new custom functions.

If you're a Windows 10 or 11 user you'll be familiar with the toast notifications that appear in the bottom right of your screen. These are a great way to get a quick message to the user without interrupting what they're doing. In this article we'll look at how to send a toast notification from PowerShell.

We could use the excellent BurntToast PowerShell module to send a toast notification, but in the interests of reducing the number of third-party modules installed on client machines we'll be using the underlying .NET APIs directly as our needs are fairly simple.

Sending toast notifications is fairly simple once you get to grips with the underlying XML schema but we want our Toasts to be next-level so we're going to make them

Creating a Notification App

This script takes a few parameters to create a Notification App. The App Name and Icon are the most important as these are what will appear in the toast notification.

Parameter documentation follows:

ParameterTypeDescription
IconURIURIThe URI of the app icon to use for the notification app registration. This will be downloaded to the working directory.
IconFileNameStringFile name to use for the app icon. Optional. If not specified, the file name from the URI will be used.
WorkingDirectoryDirectoryInfoThe working directory to use for the app icon. If not specified, 'C:\RMM\NotificationApp' will be used.
AppIdStringThe app ID to use for the notification app registration. Expected format is something like: 'CompanyName.AppName'.
AppDisplayNameStringThe app display name to use for the notification app registration.
AppIconBackgroundColorStringThe background color to use for the app icon. Optional. If not specified, the background color will be transparent. Expected format is a hex value like 'FF000000' or '0' for transparent.
ShowInSettingsIntShow the app in the Windows Settings app. Optional. If not specified, the app will not be shown in the Settings app.

The Script

New-NotificationApp.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Utilities/Windows/Notifications/RegisterNotificationApp.ps1

Generic Example

# Setup parameter hashtable.
$NotificationAppParams = @{
IconURI = 'https://homotechsual.dev/img/Icon.png'
IconFileName = 'homotechsual.png'
WorkingDirectory = 'C:\RMM\NotificationApp'
AppId = 'homotechsual.example'
AppDisplayName = 'Homotechsual Example'
AppIconBackgroundColor = 0
ShowInSettings = 1
}
Register-NotificationApp @NotificationAppParams

RMM Example

Unsurprisingly, I'm going to use NinjaOne as the example RMM here. So I've done the donkeywork that is best documented by NinjaOne themselves on the Dojo and added the script above to the NinjaOne Script Library - but what next?

Option 1: Same App for All Customers

Well the chances are that you want to use the same relatively small set of apps per customer so we'll just store a nice blob of text in the Ninja Script Library.

We could end up with a "Preset Parameter" set like this:

-IconURI https://homotechsual.dev/img/Icon.png -IconFileName homotechsual.png -WorkingDirectory C:\RMM\NotificationApp -AppId homotechsual.example -AppDisplayName "Homotechsual Example" -AppIconBackgroundColor 0 -ShowInSettings 1

You can have multiple preset parameter sets and simply select the one you want to use when you run the script or when you schedule it against a group (or however you want to run this) but what about getting more creative?

Option 2: Pull App Details per Customer

We can use Documentation fields to store the details of the app per client and then use the NinjaOne CLI to pull the details when we run the script. This is a little more work but it's worth it if you want to brand your notifications per customer.

We'll need to add a few fields to the NinjaOne Documentation tab for each client:

FieldTypeDescription
NotificationIconURIURLThe URI of the app icon to use for the notification app registration. This will be downloaded to the working directory.
NotificationAppIdStringThe app ID to use for the notification app registration. Expected format is something like: 'CompanyName.AppName'.
NotificationAppDisplayNameStringThe app display name to use for the notification app registration.
NotificationAppBackgroundColorStringThe background color to use for the app icon. Optional. If not specified, the background color will be transparent. Expected format is a hex value like 'FF000000' or '0' for transparent.

We can then use the NinjaOne CLI to pull the details for the client we're running the script against and use them to create the parameter set for the script.

This means editing our script a little to accept the parameters from the CLI and then using the NinjaOne CLI to pull the details from the Documentation tab. We're going to assume you called your Document Template Example Template and your fields named as above in the table (with spaces between words if you wish).

At the top of the script we're going to replace the entire parameter block:

[CmdletBinding()]
Param(
# The URI of the app icon to use for the notification app registration.
[Parameter(Mandatory)]
[uri]$IconURI,
# File name to use for the app icon. Optional. If not specified, the file name from the URI will be used.
[string]$IconFileName,
# The working directory to use for the app icon. If not specified, 'C:\RMM\NotificationApp\' will be used.
[System.IO.DirectoryInfo]$WorkingDirectory = 'C:\RMM\NotificationApp\',
# The app ID to use for the notification app registration. Expected format is something like: 'CompanyName.AppName'.
[Parameter(Mandatory)]
[string]$AppId,
# The app display name to use for the notification app registration.
[Parameter(Mandatory)]
[string]$AppDisplayName,
# The background color to use for the app icon. Optional. If not specified, the background color will be transparent. Expected format is a hex value like 'FF000000' or '0' for transparent.
[ValidatePattern('^(0)$|^([A-F0-9]{8})$')]
[string]$AppIconBackgroundColor = 0,
# Whether or not to show the app in the Windows Settings app. Optional. If not specified, the app will not be shown in the Settings app. Expected values are 0 or 1 (0 = false, 1 = true).
[int]$ShowInSettings = 0
)

Well that's setting up the notification app registration. Now we need to actually send a notification using the app.

Sending a Notification

So this is an entirely separate script - you can use it with the custom app you just created or you can use it with the default where notifications come from Windows PowerShell. It's up to you.

"Runs as User"

This script runs in the user's context it is unlikely it'll work running as Administrator or as a scheduled task. If you want to run it as a scheduled task, you'll need to use something like RunAsUser to run it as the user.

This script takes a few parameters to create a Notification App. The App Name and Icon are the most important as these are what will appear in the toast notification.

Parameter documentation follows:

ParameterTypeDescription
AppIdStringThe app ID to use for the notification app registration. Expected format is something like: 'CompanyName.AppName'.
NotificationImageStringThe path to the image to use for the notification.
NotificationTitleStringThe title to use for the notification.
NotificationMessageStringThe message text to use for the notification.
NotificationTypeStringThe type of notification to send. Expected values are alarm, reminder, incomingcall or default. Details on what each type looks like can be found in Microsoft's Toast schema.

The Script

Send-SimpleNotification.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Utilities/Windows/Notifications/SendNotificationSimple.ps1

Generic Example

# Setup parameter hashtable.
$NotificationParams = @{
AppId = 'homotechsual.example'
NotificationImage = 'C:\RMM\NotificationApp\NotificationIcon.png'
NotificationTitle = 'Example Notification'
NotificationMessage = 'This is an example notification.'
NotificationType = 'reminder'
}
Send-Notification @NotificationParams

Script Example

It's probably most useful to use this script as part of a larger script. For example, you could use it to send a notification when a script has finished running. You could also use it to send a notification when a scheduled task has finished running.

NotificationExample.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Utilities/Windows/Notifications/NotificationExample.ps1

This would output something like this:

Example Notification

There are many other ways you could use notifications and you can add (and handle) actions within notifications but I'm spoiling some killer upcoming blog posts.

· One min read

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Laptop role:

Field NameField TypeDescription
Driver Update: Reboot RequiredCheckboxWhether the latest driver update run requires a reboot to finalise.
Driver Update: Last RunDate/TimeThe date and time the driver update script last ran successfully.
Driver Update: Number Installed on Last RunIntegerThe number of driver updates installed on last script run.

The Script

Invoke-MUDriverUpdater.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Update%20Management/MUDriverUpdater.ps1

The Results

Driver Update Related Custom Fields

You can set this up to run on a schedule - we run this script immediately on machine onboarding and then every 7 days on a Tuesday. This doesn't always have anything to do as our Windows Update run usually handles these updates, but it's a good way to ensure that we're always up to date with the latest drivers from Microsoft Update.

· 10 min read

This post uses code from CyberDrain

This post draws on multiple posts, click the link below to check out CyberDrain.com and support Kelvin's fantastic work for the MSP community.

You will find more excellent uses for NinjaOne custom fields on the Dojo, on Stephen Murphy's blog and on Luke Whitelock's blog.

Custom fields are a great way to store arbitrary data from your devices in NinjaOne. In this post I will explore a few examples, some using code from CyberDrain, which store data in NinjaOne custom fields.

This post was updated on 2022/12/22 to add a new script to run a speedtest on a device and store the results in NinjaOne.

Battery Health

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding seven role custom fields for devices with the Windows Laptop role:

Field NameField TypeDescription
Has BatteriesCheckboxWhether the device has batteries
Battery IdentifierTextThe ID of the battery being reported on.
Battery Design CapacityIntegerThe original design capacity of the laptop's battery.
Battery Full Charge CapacityIntegerThe current fully charged capacity of the laptop's battery.
Battery Health PercentIntegerThe current percentage of battery health.
Battery Cycle CountIntegerThe number of times the battery has been cycled. That is drained fully and charged.
Additional BatteryCheckboxThe system has an additional battery, only the first is reported in the fields above.

The Script

Working on it
Ninja have confirmed that they are working on native functionality which will replace this script. Stay tuned to Ninja's release notes on the [NinjaOne Dojo](https://ninjarmm.zendesk.com) for more information.
This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2022/03/26.

This script accepts a single parameter to set the data storage directory for the script. This is where the script will store the battery information it gathers. The script will create the directory if it doesn't exist. The default is C:\RMM\Data use parameter -OutputPath to override.

Get-BatteryHealth.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/BatteryHealth.ps1

Source: https://www.cyberdrain.com/monitoring-with-powershell-monitoring-battery-health/

The Results

Battery Related Custom Fields

You can set this up to run on a schedule - we run this script monthly to keep up-to-date battery information in NinjaOne.

Domain Join Status

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Laptop role:

Field NameField TypeDescription
Domain Join StatusDrop-downThe current domain join status of the device.
Domain NameTextThe name of the currently joined AD domain.
Tenant NameTextThe name of the currently joined Azure AD tenant.

The domain join status field should be configured with the following option values:

  • Azure AD Domain
  • AD Domain
  • Hybrid Azure AD/AD Domain
  • On-premise DRS
  • No Domain

Options

We've created a dropdown field for this script, we can't set the options by name with these - so first we need to find out how NinjaOne wants us to give the selected option. To do that we're going to open a PowerShell session to any device in the role that has the field assigned and run:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
Import-Module NJCliPSh
Ninja-Property-Options domainJoinStatus

This will print a list that looks something like this:

1989092a-1ae0-4e6e-b274-50ea9618d436=Azure AD Domain
53745d33-fd5e-4c7d-bfd5-fea174a46781=No Domain
bb4c5215-ead7-47a3-8f36-f711c81580ce=On-premises DRS
c23486e9-49eb-4acb-8f9d-7f5d8711c284=AD Domain
c6e7ef80-68a1-4591-8055-5cee585f4f79=Hybrid Azure AD/AD Domain

When we want to pass the option to the field we need to use the GUID - for example 1989092a-1ae0-4e6e-b274-50ea9618d436 for Azure AD Domain.

The Script

This Script Requires Input
This script requires user input, whether in the form of variables, parameters or edits to the script itself before you can run it. Areas where you need to provide input will be indicated with:
### Inline Comments
and / or
'<MARKED STRINGS>'
Parameters will be indicated before the script block.
This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2022/05/25.

Get-DomainJoinStatus.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/DomainJoin.ps1

The Results

Domain Related Custom Fields

You can set this up to run on a schedule - we run this script monthly to keep up-to-date domain information in NinjaOne.

Autopilot Hardware Identifier

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.
"Field length"

Make sure when creating the custom field below that you set the character limit for the field to allow over 4000 characters using the "Advanced Settings" link on the field creation/edit page.

Advanced Settings Character Limit

We're adding one role custom field for devices with the Windows Desktops and Laptops role:

Field NameField TypeDescription
Autopilot HWIDSecureThe autopilot hardware identifier.

The Script

Get-AutopilotHWID.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Data%20Gathering/AutopilotHWID.ps1

The Results

HWID Related Custom Fields

We run this after hardware changes or post onboarding.

Wireless LAN Monitoring

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Desktops and Laptops role:

Field NameField TypeDescription
WLAN Disconnect ReasonsMulti-lineA list of the WLAN disconnect reasons with frequency. JSON
WLAN FailuresIntegerThe number of WLAN failures in the report.
WLAN WarningsIntegerThe number of WLAN warnings in the report.

The Script

Get-WLANStatus.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/WLANStatus.ps1

The Results

WLAN Related Custom Fields

You can set this up to run on a schedule - we run this script twice daily.

Windows 11 Readiness

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Desktops and Laptops role:

Field NameField TypeDescription
Windows 11 CapableCheckboxWhether the device is Windows 11 capable as determined by our checks.
Windows 11 Check DetailsMulti-lineThe details checked to determine Windows 11 compatibility. JSON

The Script

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/03/26.

Test-Windows11Readiness.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Data%20Gathering/Windows11Readiness.ps1

Source: https://techcommunity.microsoft.com/t5/microsoft-endpoint-manager-blog/understanding-readiness-for-windows-11-with-microsoft-endpoint/ba-p/2770866

Thanks to @gavsto for invaluable input on a couple of the checks in here - he's helped make them more robust and accurate.

The Results

Windows 11 Related Custom Fields

We ran this as a one-off job to evaluate our managed estate.

Limitations

This script won't detect compatible devices if the TPM is not enabled in the BIOS. It may not detect all compatible devices.

Windows OS Support Status

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding two role custom fields for devices with the Windows Desktops and Laptops and the Windows Server role:

Field NameField TypeDescription
Windows Active SupportCheckboxWhether the OS version is supported for feature / quality updates.
Windows Security SupportCheckboxWhether the OS version is supported for security updates.

The Script

"Shoutout"

Thanks to Khristos from the MSP Geek community for keeping an eye on EndOfLife.Date's endlessly changing property for holding the Windows build...

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2023/01/26.

Get-WindowsOSSupportStatus.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/WindowsSupportStatus.ps1

The Results

Windows Support Status Related Custom Fields

We run this on a schedule so it runs just after our OS patching applies.

Limitations

Won't work with Insider/Preview versions - hasn't been tested on Windows 7 / 8 or 8.1. Has been tested on Server 2012 R2, 2022 and Windows 10 and 11.

SpeedTest (LibreSpeed)

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Desktops and Laptops and the Windows Server role:

Field NameField TypeDescription
Server UsedTextWhich server was used for the last speedtest run.
Download SpeedDecimalThe download speed from the last speedtest run in megabits per second.
Upload SpeedDecimalThe upload speed from the last speedtest run in megabits per second.

The Script

"Issues found"

This script seems to have some issues where the librespeed-cli will return null for tests intermittently - this was due to service issues with LibreSpeed themselves. An alternative version using the Ookla speedtest CLI is available below.

This Script Was Updated

This script was updated after being published, if you're using it please compare the version you have with the version available here.

This script was last updated on 2022/12/31.

Test-ConnectionSpeedwithLibreSpeed.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/Speed%20Test/LibreSpeed.ps1

Parameters

You can pass a few different parameters to this script to tune it's behaviour. These are:

Parameter NameTypeDescription
LibreSpeedEXEPathStringThe path to the LibreSpeed executable. Defaults to C:\RMM\Bin\
NoUpdateSwitchIf specified, the script will not attempt to update or download LibreSpeed.
ForceUpdateSwitchIf specified, the script will download LibreSpeed even if it's already up to date.
CLISwitchesStringA string of switches to pass to the LibreSpeed CLI. Defaults to --json.

For documentation on the CLI switches available see the LibreSpeed CLI documentation.

The Results

Speed Test Related Custom Fields

In this screenshot we've added a separator and a nice custom title with an emoji to make it look a bit nicer.

⏱️SPEEDTEST

We run this on demand when we want to refresh information on the device - we don't run it on a schedule at present.

Limitations

Requires the ability to run the LibreSpeed CLI tool on the device. The script can fetch the latest version and will do so automatically.

SpeedTest (Ookla Speedtest)

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're adding three role custom fields for devices with the Windows Desktops and Laptops and the Windows Server role:

Field NameField TypeDescription
Server UsedTextWhich server was used for the last speedtest run.
Download SpeedDecimalThe download speed from the last speedtest run in megabits per second.
Upload SpeedDecimalThe upload speed from the last speedtest run in megabits per second.

The Script

Test-ConnectionSpeedwithOoklaSpeedtest.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/Speed%20Test/OoklaSpeedtest.ps1

Parameters

You can pass a few different parameters to this script to tune it's behaviour. These are:

Parameter NameTypeDescription
OoklaSpeedtestURIStringThe URI to the Ookla Speedtest CLI. Defaults to the 1.2.0 version current when written.
OoklaSpeedtestEXEPathStringThe path to the Ookla Speedtest executable. Defaults to C:\RMM\Bin\
NoUpdateSwitchIf specified, the script will not attempt to update or download Ookla Speedtest.
ForceUpdateSwitchIf specified, the script will download Ookla Speedtest even if it's already up to date.
CLISwitchesStringA string of switches to pass to the Ookla Speedtest CLI. Defaults to --format=json --accept-license --accept-gdpr.

For documentation on the CLI switches available you should download the latest version of the CLI and check out the speedtest.md file in the Zip or run speedtest.exe --help.

The Results

Speed Test Related Custom Fields

In this screenshot we've added a separator and a nice custom title with an emoji to make it look a bit nicer.

⏱️SPEEDTEST

We run this on demand when we want to refresh information on the device - we don't run it on a schedule at present.

Limitations

Requires the ability to run the Ookla Speedtest CLI tool on the device. The script cannot automatically determine the latest version at this time - please pass an updated URL if you wish to use a later version before the script is updated.

· 2 min read

Background information

Ninja doesn't currently support native AV monitoring via Windows Security Center, integrated AV packages are monitored but what if you need more?

Creating Fields

Creating custom fields in NinjaOne
To create a custom field in NinjaOne go to Administration > Devices and select either Role Custom Fields or Global Custom Fields then select Add.

  • Role Custom Fields are custom fields that are specific to a device role.
  • Global Custom Fields are custom fields that are applicable to all devices and/or to a location and/or organisation
Make sure you add the fields to the roles you want to use them in at Administration > Devices > Roles (for role custom fields).

When you create your custom field you need to make sure that you set the Scripts permission to ensure that you can read or write to the field from your scripts - as appropriate for the script you're using.

We're going to create one role custom field for devices with the Windows Desktop or Windows Laptop role:

Field NameField TypeDescription
Detailed AV StatusMulti-LineOutput for each configured AV on the system including name and status information.

The Script

Windows Server

This script won't work on Windows Server operating systems as they lack a CIM or WMI interface to the Windows Security Center which allows us to easily programmatically query the AV status.

Get-WindowsAVStatus.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Monitoring/WindowsAVStatus.ps1

Monitoring

The script includes support for two monitors.

AV Not Enabled

Setup a script result condition monitor that runs this script with a check for an exit code of 1.

AV Not Enabled Condition

AV Not Enabled Monitor

AV Not Up-To-Date

Setup a second script result condition monitor that runs this script with a check for an exit code of 2.

AV Not Enabled Condition

AV Not Enabled Monitor

· 4 min read

Secure Application Model

For this post you'll want a secure application model Azure AD application configured. Use these instructions from Gavin Stone's Gavsto.com blog.

CIPP Inspired

The code in this script was inspired by functionality in the CyberDrain Improved Partner Portal (CIPP). Need to manage M365 tenants - CIPP is a fantastic open-source solution for M365 management for single tenants and Microsoft CSP Partners alike.

Background information

In my quest to stop having to use Windows Sandbox or VMs to access Exchange Online via PowerShell (we have WinRM basic auth disabled) I've been looking at solutions using the Exchange Online REST API to run commands against our customer tenants. The result of my efforts is a pretty complex script that introduces a method to run any PowerShell script in the context of a set of customer tenants.

caution

You need to have configured the Secure Application Model according to the instructions linked above, you will need your SAM tokens etc accessible for this guide.

Securing Credentials in Scripts
Do not store credentials directly in scripts. You should utilise services like Azure KeyVault or other PAM solutions to secure credentials used in scripts, including this one.

The script

Invoke-EORESTDelegated.ps1
https://github.com/homotechsual/Blog-Scripts/blob/main/Microsoft%20365/Exchange%20Online/EORest.ps1

Building Parameters

Personally I find passing parameters as a HashTable (splatting) easier to read and cleaner see what's happening so that's what I'm doing to explain the parameters for the script:

Parameter Splat
$EORESTParameters = @{
PartnerTenantId = (Get-AzKeyVaultSecret -VaultName 'Homotechsual' -Name 'EORESTTenantId' -AsPlainText) # Your partner tenant id.
ApplicationId = (Get-AzKeyVaultSecret -VaultName 'Homotechsual' -Name 'EORESTClientId' -AsPlainText) # Your SAM application / client id.
ApplicationSecret = (Get-AzKeyVaultSecret -VaultName 'Homotechsual' -Name 'EORESTClientSecret' -AsPlainText) # Your SAM application / client secret.
GraphRefreshToken = (Get-AzKeyVaultSecret -VaultName 'Homotechsual' -Name 'EORESTRefreshToken' -AsPlainText) # The Graph refresh t oken provided by the SAM app creation script.
ExchangeRefreshToken = (Get-AzKeyVaultSecret -VaultName 'Homotechsual' -Name 'EORESTExchangeRefreshToken' -AsPlainText) # The Exchange refresh token provided by the SAM app creation script.
UPN = '[email protected]' # The UPN of the user used to authorise your SAM tokens.
IncludeTenants = @('test1.onmicrosoft.com') # Run only on the tenants listed - using the tenant's `DefaultDomainName`. This input is NOT validated.
ExcludeTenants = @('test1.onmicrosoft.com') # Run on the all tenant except those listed - using the tenant's `DefaultDomainName`.
}

ScriptBlock

The -ScriptBlock parameter passed to the script runs the commands given against each selected tenant's Exchange Online. You should use the Invoke-EORequest cmdlet in the script block to send commands to ExchangeOnline. For example to get mailboxes:

-ScriptBlock { Invoke-EORequest -Commandlet 'Get-Mailbox' }

More examples are given below.

Result structure

Before returning values we add a property to object containing the customer's tenant id. This property is called EORCustomerId and is the tenant GUID. You can use this to group results by the tenant they relate to.

| Group-Object -Property 'EORCustomerId'

Example uses

These examples assume use of the parameter splat above

Get Mailbox Plans

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock { Invoke-EORequest -Commandlet 'Get-MailboxPlan' }

Get Shared, Room, Equipment, Group and Team mailboxes

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock { Invoke-EORequest -Commandlet 'Get-Mailbox' -Parameters @{ Filter = "((RecipientTypeDetails -eq 'SharedMailbox') -or (RecipientTypeDetails -eq 'RoomMailbox') -or (RecipientTypeDetails -eq 'EquipmentMailbox') -or (RecipientTypeDetails -eq 'GroupMailbox') -or (RecipientTypeDetails -eq 'TeamMailbox'))" } }

Get last login time.

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock {
$Mailboxes = Invoke-EORequest -Commandlet 'Get-Mailbox' -Parameters @{ ResultSize = 'Unlimited' }
ForEach ($Mailbox in $Mailboxes) { Invoke-EORequest -Commandlet 'Get-MailboxStatistics' -Parameters @{ Identity = $Mailbox.UserPrincipalName } | Select-Object -Property DisplayName, LastLoggedOnUserAccount, LastLogonTime }
}

Get mailboxes not logged into in the last 90 days.

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock {
$Mailboxes = Invoke-EORequest -Commandlet 'Get-Mailbox' -Parameters @{ ResultSize = 'Unlimited' }
ForEach ($Mailbox in $Mailboxes) { Invoke-EORequest -Commandlet 'Get-MailboxStatistics' -Parameters @{ Identity = $Mailbox.UserPrincipalName } | Where-Object { $_.LastLogonTime -lt (Get-Date).AddDays(-90) } | Select-Object -Property DisplayName, LastLoggedOnUserAccount, LastLogonTime }
}

Get inactive mailboxes.

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock {
Invoke-EORequest -Commandlet 'Get-Mailbox' -Parameters @{ InactiveMailboxOnly = $True }
}

Set hidden from address list for all mailboxes.

.\Invoke-EORESTDelegated.ps1 @EORESTParameters -ScriptBlock {
$Mailboxes = Invoke-EORequest -Commandlet 'Get-Mailbox' -Parameters @{ ResultSize = 'Unlimited' }
ForEach ($Mailbox in $Mailboxes) { Invoke-EORequest -Commandlet 'Set-Mailbox' -Parameters @{ Identity = $Mailbox.UserPrincipalName; HiddenFromAddressListEnabled = $True } }
}