Skip to main content

4 posts tagged with "Scripting"

View All Tags

· 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.



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 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.


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


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 = @('') # Run only on the tenants listed - using the tenant's `DefaultDomainName`. This input is NOT validated.
ExcludeTenants = @('') # Run on the all tenant except those listed - using the tenant's `DefaultDomainName`.


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 } }

· One min read

I'm the author of the NinjaOne PowerShell module so when I was looking for a quick way to identify duplicate devices I turned to that module and the NinjaOne API to find a solution.

About this script

This script is a quick way to identify duplicate devices in your NinjaOne instance. It can return a full listing of all devices grouped by serial number, it can also just return the ID, last contact date and serial number for the device with the oldest last contact date.

The script


At this time it's not possible to remove devices via the NinjaOne API so this script will only return the duplicate devices - you'll have to arrange to remove these devices manually.

If you want all devices with duplicates:

Get-NinjaOneDuplicateDevices -All

or just the older duplicated devices:

Get-NinjaOneDuplicateDevices -Duplicates

· 2 min read

Did you know, when you have a retention policy configured for OneDrive in Microsoft 365 you lose the ability to recursively delete folders. The OneDrive client often handles this by recursing through the folder tree to remove the online copies but sometimes that doesn't work and in those cases you can end up stuck with a folder tree that you would have to manually recurse to remove the offending folders by first emptying them entirely before OneDrive will let you delete them.

Well that's never going to happen ;-)

Enter PowerShell.

Required Modules

You'll need the PnP.PowerShell module for this script. Install with Install-Module 'PnP.PowerShell'

The script


Invoking the script

The script is looking for three parameters, an example invocation to remove a synced "Documents/PowerShell/Modules" folder in Satya Nadella's OneDrive would be:

Double the Documents

You'll notice below that the /Documents folder in the path is doubled. This is because the first folder in the OneDrive path is always /Documents then your path starts. In this example we're removing the /Documents/PowerShell/Modules folder.

Remove-OneDriveFolderRecursively -OneDriveHost '' -SitePath '/personal/satya_nadella_microsoft_com' -FolderPath '/Documents/Documents/PowerShell/Modules'

Hope this helps!

This post was inspired by a script posted on Microsoft's TechCommunity here...