Automating Nerdio Manager for MSP with the NMM-PSModule
Nerdio Manager for MSP already provides a very complete portal experience for managing Azure Virtual Desktop, Windows 365, scripted actions, users, host pools, backups, cost management, and many other operational areas. For day-to-day work, the portal is often the right place to be. But as soon as the same action needs to be repeated across multiple customers, tenants, accounts, or host pools, PowerShell becomes the better place to standardize the workflow.
This is where the NMM-PSModule becomes useful. The module is a PowerShell client generated from the Nerdio Manager for MSP OpenAPI specification. Instead of writing raw REST calls every time, the module exposes the NMM API as discoverable PowerShell functions. Authentication, base URL handling, request construction, body serialization, path escaping, and client-side filtering are handled by the module, allowing the automation script itself to focus on the operational task.
In this post, we will walk through the module structure, authentication options, command discovery, filtering, and a few practical automation patterns that make the module interesting for MSP environments.
Important: Be aware that the NMM-PSModule is currently a 0.1.0 (alpha) release and has the status “work in progress”. Please keep that in mind when testing this.
Where to get NMM-PSModule
The NMM-PSModule is hosted as a public repository on GitHub, which can be found here:
https://github.com/ivandemes/NMM-PSModule
Getting Started
Before using the module, the NMM REST API needs to be enabled and configured in Nerdio Manager for MSP. In the NMM portal, this is done from the REST API integration area where the required application registration, API scope, and permissions are configured. Once the API is available, the module can authenticate by using either an existing bearer token or OAuth 2.0 client credentials.
After the module is available locally, import it into a PowerShell 7 session:
|
1 2 3 |
Import-Module '.\NMM-PSModule\NMM-PSModule.psd1' |
For production use, the module would typically be installed into a module path or packaged through an internal automation repository. For testing and development, importing it directly from the repository is usually enough.
Authenticating to NMM
The most important command to start with is Connect-NMMApi . This command creates an authenticated connection object containing the NMM base URI and access token. By default, it stores the connection as the current module connection, so later commands can use it automatically. If you prefer to pass connection objects explicitly, the -NoDefault switch can be used.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
NAME Connect-NMMApi SYNOPSIS Creates and optionally saves an authenticated NMM API connection. SYNTAX Connect-NMMApi -BaseUri <Uri> -AccessToken <String> [-NoDefault] [<CommonParameters>] Connect-NMMApi -BaseUri <Uri> -ClientId <String> -ClientSecret <Object> [-TenantId <String>] [-OAuthTokenUri <Uri>] [-TokenPath <String>] [-Scope <String>] [-NoDefault] [<CommonParameters>] DESCRIPTION Uses an existing bearer token or obtains one with the OAuth 2.0 client credentials flow. PARAMETERS -BaseUri <Uri> The absolute URI of the NMM instance, for example https://example.getnerdio.com. Required? true Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -AccessToken <String> An existing OAuth bearer token. Required? true Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -ClientId <String> The OAuth client identifier. Required? true Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -ClientSecret <Object> The OAuth client secret. Accepts a plain string for automation compatibility or a SecureString to reduce accidental exposure. Required? true Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -TenantId <String> The Microsoft Entra tenant ID shown in the NMM REST API credentials. When supplied, tokens are requested directly from Microsoft Entra ID. Required? false Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -OAuthTokenUri <Uri> An explicit OAuth token endpoint. This takes precedence over TenantId and TokenPath. Required? false Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -TokenPath <String> The legacy token endpoint path relative to BaseUri. For unattended authentication, use TenantId or OAuthTokenUri. Required? false Position? named Default value /api/v1/msp/rest-api/token Accept pipeline input? false Aliases Accept wildcard characters? false -Scope <String> An optional OAuth scope. Required? false Position? named Default value Accept pipeline input? false Aliases Accept wildcard characters? false -NoDefault [<SwitchParameter>] Returns the connection without making it the module's current connection. Required? false Position? named Default value False Accept pipeline input? false Aliases Accept wildcard characters? false <CommonParameters> This cmdlet supports the common parameters: Verbose, Debug, ErrorAction, ErrorVariable, WarningAction, WarningVariable, OutBuffer, PipelineVariable, and OutVariable. For more information, see about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216). |
Connect-NMMApi supports an existing access token as well as OAuth client credentials, including a Tenant ID based Microsoft Entra token endpoint.
If you already have a bearer token, authentication can be as simple as this:
|
1 2 3 4 5 |
$connection = Connect-NMMApi ` -BaseUri 'https://example.getnerdio.com' ` -AccessToken $accessToken |
For unattended automation, the client credentials flow is usually the more interesting option. When the Tenant ID is supplied, the module requests a token directly from Microsoft Entra ID:
|
1 2 3 4 5 6 7 8 9 10 |
$clientSecret = Read-Host 'NMM API client secret' -AsSecureString $connection = Connect-NMMApi ` -BaseUri 'https://example.getnerdio.com' ` -TenantId '00000000-0000-0000-0000-000000000000' ` -ClientId '11111111-1111-1111-1111-111111111111' ` -ClientSecret $clientSecret ` -Scope 'api://your-nmm-api-scope/.default' |
The module also accepts a plain string for -ClientSecret , which can be useful in automation systems where secrets are injected at runtime. In production, avoid hardcoding secrets in scripts. Store them in a secure location such as Azure Key Vault, an automation account secret store, a CI/CD secret variable, or another credential management solution used by your organization.
The connection can be checked without exposing the access token:
|
1 2 3 |
Get-NMMApiConnection |
The local tests for the module explicitly validate that Get-NMMApiConnection shows connection status and base URI information without returning the access token as a visible property. That is a small but important detail when working in shared terminals, screen recordings, or support sessions.
Discovering Available Commands
A module with more than 350 exported commands needs good discovery. The helper command Get-NMMCommand provides a searchable command index. It can show commands grouped by category, filter by name, verb, category, or API path, and return either formatted text or structured objects.
|
1 2 3 4 5 6 |
Get-NMMCommand Get-NMMCommand -Name '*HostPool*' -Verb Get Get-NMMCommand -ApiPath '*/backup/*' Get-NMMCommand -Category HostPool -AsObject |
The command discovery view is especially useful when looking for the right command family, such as HostPool, Backup, UAM, Security, Billing, or Infrastructure.
When -AsObject is used, the results can be filtered, exported, grouped, or searched further:
|
1 2 3 4 5 |
Get-NMMCommand -AsObject | Where-Object Category -eq 'Backup' | Select-Object Name, Method, ApiPath, Synopsis |
This is useful when building runbooks because it lets you discover the PowerShell command and the underlying API path at the same time. If you already know the REST endpoint from the NMM Swagger documentation, searching by API path is often the fastest way to find the generated PowerShell function.
Reading Accounts and Filtering Results
A common first test after connecting is retrieving the NMM accounts list:
|
1 2 3 |
Get-NMMAccounts |
Many generated Get-NMM... commands include a -Filter parameter when the API response is an array. In the local module tests, 75 GET commands are expected to include this client-side filter. The filter accepts either a script block or a simple string expression.
|
1 2 3 4 5 |
Get-NMMAccounts -Filter { $_.name -like 'Production*' } Get-NMMAccounts -Filter "name -eq 'Contoso Demo'" |
Client-side filtering should not replace API-native query parameters when the API provides them, but it is very convenient for quick operational workflows. For example, an MSP runbook can first retrieve the account by name and then pipe the selected account into the next account-scoped command.
Using Pipeline Binding for Account-Scoped Operations
One of the nicest usability details in the module is pipeline binding for path parameters. The test suite verifies that an account object returned by Get-NMMAccounts can be piped into an account-scoped command, where the account object’s Id is bound to the AccountId path parameter.
|
1 2 3 4 |
Get-NMMAccounts -Filter "name -eq 'Contoso Demo'" | Get-NMMAccountsByAccountIdSecureVariables |
This pattern is very useful when writing customer-centric scripts. Instead of manually copying account IDs between commands, the pipeline can carry the selected object into the next step. That makes the script easier to read and reduces the chance of running an action against the wrong customer account.
Working with Generated API Commands
Most commands follow a consistent structure. Path parameters are exposed as command parameters, query parameters are added when available, and request bodies are passed through -InputObject . Objects and hashtables are automatically serialized to JSON before being sent to the API.
Each generated command has corresponding Markdown documentation under docs/Commands , including synopsis, notes, parameters, examples, and the related REST API path.
For example, the tenant linking command for account provisioning is exposed as New-NMMAccountprovisioningLinkTenant . The generated help includes the same operational details that matter when using the API directly, such as subscription ID, Azure Resource Manager access token, Microsoft Graph access token, company name, Active Directory type, limited access mode, and desktop deployment options.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$body = @{ subscriptionId = '00000000-0000-0000-0000-000000000000' azureAccessToken = $armAccessToken graphAccessToken = $graphAccessToken companyName = 'Contoso AVD' activeDirectoryType = 'ExistingAD' limitedAccessEnabled = $false desktopDeploymentOptions = @{ wvd = $true selfManagedCloudPc = $false endpointManagedCloudPc = $false endpointManagedWithIntune = $false } } $response = New-NMMAccountprovisioningLinkTenant -InputObject $body |
Because the generated commands use SupportsShouldProcess for write operations, you can use standard PowerShell safety patterns such as -WhatIf where supported by the command. For actions that change customer environments, this is an important habit to keep.
Building a Practical Automation Flow
A typical MSP automation flow can be built in layers. First, connect to the NMM API. Then identify the customer account. After that, retrieve or update the specific resource you need to manage.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$connection = Connect-NMMApi ` -BaseUri 'https://example.getnerdio.com' ` -TenantId $tenantId ` -ClientId $clientId ` -ClientSecret $clientSecret ` -Scope $scope $account = Get-NMMAccounts -Filter "name -eq 'Contoso Demo'" $hostPools = $account | Get-NMMAccountsByAccountIdHostPool $hostPools | Select-Object name, resourceGroup, subscriptionId |
From there, the same pattern can be extended into host pool operations, scripted actions, backup validation, user management, secure variable maintenance, or UAM workflows. The key benefit is that the raw REST details are handled consistently by the module, while the automation can stay readable for the engineers who need to maintain it later.
Understanding the Request Layer
Behind the generated commands, the private request helper performs the repetitive REST work. It resolves the active connection, escapes path parameter values, builds query strings, adds the bearer token authorization header, serializes request bodies as compressed JSON, and calls Invoke-RestMethod .
This is exactly the kind of code that becomes noisy and error-prone when copied into every script. Centralizing it inside the module makes the operational scripts smaller and more consistent. It also gives you one place to improve behavior later, for example if you want to add logging, retry handling, correlation IDs, or more detailed error reporting.
Useful Command Areas
The command set is broad, but the following areas are especially relevant for MSP automation:
- Account provisioning: link tenants, connect existing Active Directory, link networks, configure storage, and refresh PSA customer mappings.
- Host pools: create host pools, retrieve settings, manage hosts, run scripts, control sessions, configure autoscale, update tags, and manage FSLogix settings.
- Desktop images: create images from VMs or gallery images, start or stop images, run scripts, validate, clone, schedule, and set images.
- Automation: work with autoscale profiles, schedules, scripted actions, and NMM jobs.
- Backup and recovery: inspect protected items, recovery points, vaults, policies, and backup operations.
- Security: manage app role assignments, secure variables, MFA-related actions, vulnerabilities, and security recommendations.
- UAM: manage application groups, policies, private repositories, WinGet repositories, shell apps, assignments, and deployment state.
- Billing and usage: retrieve invoices, usage, cost estimates, reservation recommendations, and reservation details.
That coverage makes the module useful beyond a single onboarding task. It can become the foundation for scheduled compliance checks, customer baseline validation, standardized host pool reporting, bulk maintenance, or integration with ITSM and documentation systems.
Regenerating the Module
The repository includes build tooling and the original swagger.json file. The module is designed to be regenerated from the OpenAPI definition, which is important because REST APIs evolve over time. When Nerdio adds or changes endpoints, the generated command set can be rebuilt from an updated specification instead of manually maintaining hundreds of wrapper functions.
The repository also contains tests that validate the generated command contract. These tests check that one command exists for every OpenAPI operation, command names are unique, HTTP methods map to the expected PowerShell verbs, generated help exists, filtering is added where expected, and connection details avoid exposing the access token. For a generated module, those checks are valuable because they help catch broad regressions quickly.
Important Considerations
- Use secure secret storage. The module supports SecureString and runtime-provided secrets, but secret handling is still the responsibility of the automation platform.
- Prefer API-side filtering when available. Client-side filtering is useful, but API-native query parameters are usually more efficient for large datasets.
- Be careful with generated command names. Some names are long because they mirror API paths. Use Get-NMMCommand to find the right command rather than guessing.
- Use -WhatIf and test tenants. Many commands can change customer environments. Validate scripts against non-production accounts first.
- Plan for job polling. Some NMM operations start asynchronous jobs. Scripts should check job status before continuing to dependent steps.
- Keep the Swagger source current. The module is only as current as the OpenAPI definition used to generate it.
Wrapping Up
The NMM-PSModule is a practical way to bring Nerdio Manager for MSP automation into PowerShell. It does not remove the need to understand the NMM API, but it does remove a lot of repetitive REST plumbing. For MSPs managing multiple customers, that can make the difference between a one-off script and a repeatable operational workflow.
The combination of generated API coverage, OAuth authentication, discoverable commands, connection reuse, generated documentation, filtering, and pipeline-friendly account handling makes the module a solid foundation for real-world Nerdio automation. Whether the goal is customer onboarding, reporting, host pool management, backup validation, UAM maintenance, or scheduled operational checks, the module gives PowerShell engineers a consistent starting point.
As with any automation that can affect customer environments, start small, test carefully, and build reusable patterns around authentication, logging, job polling, and error handling. Once those basics are in place, the NMM REST API becomes much easier to use at scale from PowerShell.



