Overview
Setting up EntraID app registration, adding as service account in NPS, and adding resource.
Description
This pwsh 7 script will do everything you need to setup EntraID resource scans and activities. Just run via powershell 7.x or higher.
# =============================================================================
# Netwrix Privilege Secure (NPS-AM) Entra ID Setup Script
#
# Purpose: Create Entra ID app registration for NPS-AM, register in NPS, and create Azure AD resource
# Author: Adam S. with help from Claude 3.7 Sonnet Extended
# Date: February 27, 2025
# =============================================================================
# Script to create Entra ID app registration for NPS-AM, register in NPS, and create Azure AD Tenant resource
# Using Microsoft Graph PowerShell -Will check and install as required.
# MUST USE POWERSHELL 7.x or higher
# Prompt for NPS host URL
$NPSHost = Read-Host -Prompt "NPS-AM Host URL (press Enter for https://localhost:6500)"
if ([string]::IsNullOrWhiteSpace($NPSHost)) {
$NPSHost = "https://localhost:6500"
Write-Host "Using default host: $NPSHost" -ForegroundColor Yellow
}
# Check for and install required modules if not present
Write-Host "Checking for required Microsoft Graph modules..." -ForegroundColor Yellow
$requiredModules = @(
"Microsoft.Graph.Authentication",
"Microsoft.Graph.Applications",
"Microsoft.Graph.Identity.DirectoryManagement",
"Microsoft.Graph.Users.Actions",
"Microsoft.Graph.Users"
)
foreach ($module in $requiredModules) {
if (!(Get-Module -ListAvailable -Name $module)) {
Write-Host "Installing $module module..." -ForegroundColor Yellow
Install-Module -Name $module -Force -AllowClobber -Scope CurrentUser
}
}
# Import modules
foreach ($module in $requiredModules) {
Import-Module $module
}
# Connect to Microsoft Graph
try {
Write-Host "Connecting to Microsoft Graph. This will open a login window..." -ForegroundColor Yellow
Connect-MgGraph -Scopes "Application.ReadWrite.All", "Directory.ReadWrite.All", "RoleManagement.ReadWrite.Directory" -NoWelcome
Write-Host "Successfully connected to Microsoft Graph" -ForegroundColor Green
}
catch {
Write-Host "Error connecting to Microsoft Graph: $_" -ForegroundColor Red
Write-Host "Please make sure you have the necessary permissions and can access Microsoft Graph API." -ForegroundColor Yellow
exit
}
# Get tenant information
try {
$tenant = Get-MgOrganization
$tenantId = $tenant.Id
$domains = Get-MgDomain
$initialDomain = ($domains | Where-Object { $_.IsInitial -eq $true }).Id
Write-Host "Tenant ID: $tenantId" -ForegroundColor Cyan
Write-Host "Email Domain: $initialDomain" -ForegroundColor Cyan
}
catch {
Write-Host "Error getting tenant details: $_" -ForegroundColor Red
exit
}
# Define application parameters
$appName = "Netwrix Privilege Secure"
$redirectUri = "$NPSHost" # Needed for admin consent flow
# Create the application registration
try {
Write-Host "Creating app registration '$appName'..." -ForegroundColor Yellow
# Define required resource access for Microsoft Graph
$graphResourceId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph AppId
# Define application permissions needed
$appPermissions = @(
@{
Id = "19dbc75e-c2e2-444c-a770-ec69d8559fc7" # Directory.ReadWrite.All
Type = "Role"
},
@{
Id = "62a82d76-70ea-41e2-9197-370581804d09" # Group.ReadWrite.All
Type = "Role"
},
@{
Id = "741f803b-c850-494e-b5df-cde7c675a1ca" # User.ReadWrite.All
Type = "Role"
},
@{
Id = "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8" # RoleManagement.ReadWrite.Directory
Type = "Role"
},
@{
Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" # User.Read
Type = "Scope"
}
)
# Prepare resource access object for Microsoft Graph
$reqResourceAccess = @{
ResourceAppId = $graphResourceId
ResourceAccess = $appPermissions
}
# Create the application
$params = @{
DisplayName = $appName
SignInAudience = "AzureADMyOrg"
Web = @{
RedirectUris = @($redirectUri)
}
RequiredResourceAccess = @($reqResourceAccess)
}
$app = New-MgApplication -BodyParameter $params
$appId = $app.AppId
Write-Host "App registration created with Client ID: $appId" -ForegroundColor Green
}
catch {
Write-Host "Error creating application: $_" -ForegroundColor Red
exit
}
# Create a service principal for the application
try {
Write-Host "Creating service principal..." -ForegroundColor Yellow
$params = @{
AppId = $appId
}
$servicePrincipal = New-MgServicePrincipal -BodyParameter $params
$spId = $servicePrincipal.Id
Write-Host "Service principal created with ID: $spId" -ForegroundColor Green
}
catch {
Write-Host "Error creating service principal: $_" -ForegroundColor Red
}
# Create client secret
try {
Write-Host "Creating client secret..." -ForegroundColor Yellow
# Set the expiration date to 2 years from now
$startDate = Get-Date
$endDate = $startDate.AddYears(2)
# Create password credential
$params = @{
PasswordCredential = @{
DisplayName = "Netwrix Secret"
EndDateTime = $endDate
}
}
$secretObject = Add-MgApplicationPassword -ApplicationId $app.Id -BodyParameter $params
$secretValue = $secretObject.SecretText
Write-Host "Secret Value: $secretValue" -ForegroundColor Cyan
}
catch {
Write-Host "Error creating client secret: $_" -ForegroundColor Red
exit
}
# Grant admin consent by launching browser automatically
Write-Host "Launching browser to grant admin consent..." -ForegroundColor Yellow
$adminConsentUri = "https://login.microsoftonline.com/$tenantId/adminconsent?client_id=$appId"
Write-Host "If you are not redirected to grant admin consent, please manually open this URL:" -ForegroundColor Cyan
Write-Host $adminConsentUri -ForegroundColor Cyan
# Launch the default browser with the admin consent URL
try {
Start-Process $adminConsentUri
Write-Host "Browser launched. Please complete the admin consent process in your browser." -ForegroundColor Yellow
Write-Host "After consent is granted you are redirected to localhost and you may continue. Press Enter to continue..."
$null = Read-Host
}
catch {
Write-Host "Could not automatically launch browser. Please manually open this URL:" -ForegroundColor Red
Write-Host $adminConsentUri -ForegroundColor Cyan
Write-Host "After consent is granted you are redirected to localhost and you may continue. Press Enter to continue..."
$null = Read-Host
}
# We need to assign User Administrator role (newer versions have a different way to do this)
# In newer versions of Microsoft Graph, we need to check the PowerShell version to see which method to use
Write-Host "Assigning User Administrator role to the service principal..." -ForegroundColor Yellow
try {
# Get the User Administrator role
$roles = Get-MgDirectoryRole
$userAdminRole = $roles | Where-Object { $_.DisplayName -eq "User Administrator" }
if ($null -eq $userAdminRole) {
Write-Host "User Administrator role not found. Activating template..." -ForegroundColor Yellow
# Get the role template
$roleTemplates = Get-MgDirectoryRoleTemplate
$userAdminTemplate = $roleTemplates | Where-Object { $_.DisplayName -eq "User Administrator" }
# Activate the role from template
$params = @{
RoleTemplateId = $userAdminTemplate.Id
}
$userAdminRole = New-MgDirectoryRole -BodyParameter $params
}
# Assign the role to the service principal
$params = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$spId"
}
New-MgDirectoryRoleMemberByRef -DirectoryRoleId $userAdminRole.Id -BodyParameter $params
Write-Host "User Administrator role assigned successfully" -ForegroundColor Green
}
catch {
Write-Host "Error assigning User Administrator role: $_" -ForegroundColor Red
Write-Host "You may need to manually assign this role in the Azure portal" -ForegroundColor Yellow
}
# Output the important information to be saved
Write-Host "`n----------- ENTRA ID APP INFORMATION -----------" -ForegroundColor Magenta
Write-Host "Tenant ID (resource page): $tenantId" -ForegroundColor Cyan
Write-Host "Email Domain (resource page): $initialDomain" -ForegroundColor Cyan
Write-Host "App ID (service account page): $appId" -ForegroundColor Cyan
Write-Host "App Secret (service account page): $secretValue" -ForegroundColor Cyan
Write-Host "-----------------------------------------------`n" -ForegroundColor Magenta
Write-Host "App registration process completed!" -ForegroundColor Green
Write-Host "Now continuing to create NPS credential..." -ForegroundColor Yellow
# Script Part 2: Use the app registration info to create NPS credential
# Set the EntraID Service Account name and description
$Name = 'Entra ID Service Account for ' + $initialDomain
$Description = 'Service account for Azure Entra ID integration'
# Prompt for NPS login credentials
$Username = Read-Host -Prompt "Enter a NPS admin username (domain\username)"
$Password = Read-Host -Prompt "Password" -MaskInput
$Login = @{
Login = $Username
Password = $Password
}
# Cookie container for multi-factor authentication
$WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
try {
Write-Host "Authenticating NPS Password..." -ForegroundColor Yellow
$TokenResponse = Invoke-RestMethod -Uri "$NPSHost/signinBody" -Method POST -Body (ConvertTo-Json $Login) -Headers @{ "Content-Type" = "application/json" } -SessionVariable WebSession -SkipCertificateCheck
$Token = $TokenResponse
Write-Host "NPS Password authentication successful" -ForegroundColor Green
}
catch {
Write-Error "NPS Password authentication failed: $_"
# For better debugging
if ($_.ErrorDetails.Message) {
Write-Error "Error details: $($_.ErrorDetails.Message)"
}
Write-Host "Please check your username and password and try again." -ForegroundColor Red
exit
}
# Prompt for MFA code
$MfaCodeInput = Read-Host -Prompt "Enter your MFA code (If you do not have 2FA enabled, enter 0 and press Enter)"
$MfaCode = @{
MfaCode = $MfaCodeInput
}
# Second authentication step for MFA with better error handling
try {
Write-Host "Submitting MFA code..." -ForegroundColor Yellow
$MfaToken = Invoke-RestMethod -Uri "$NPSHost/signin2fa" -Method POST -Body (ConvertTo-Json $MfaCode) -Headers @{ Authorization = "Bearer $Token"; "Content-Type" = "application/json" } -WebSession $WebSession -SkipCertificateCheck
Write-Host "MFA authentication successful" -ForegroundColor Green
}
catch {
Write-Error "Second authentication step for MFA failed: $_"
# For better debugging
if ($_.ErrorDetails.Message) {
Write-Error "Error details: $($_.ErrorDetails.Message)"
}
Write-Host "Please check your MFA code and try again." -ForegroundColor Red
exit
}
# Set headers with the authorization token
$Headers = @{
Authorization = "Bearer $MfaToken"
"Content-Type" = "application/json"
}
# Create the credential payload using the extracted AppID and AppSecret
$CredentialPayload = @{
username = $appId # Using the AppID from the Entra ID registration
password = $secretValue # Using the AppSecret from the Entra ID registration
name = $Name # Using user input for the name
description = $Description # Using user input for the description
type = 0
platformId = "319034e0-f7eb-4261-8624-c55a086528fc" # Azure/Entra ID platform ID
authenticationMethod = 0
}
# Check for PowerShell Core and handle SkipCertificateCheck parameter
$currentPSEdition = $PSVersionTable.PSEdition
$currentPSVersion = $PSVersionTable.PSVersion.Major
# Prepare parameters for Invoke-RestMethod based on PowerShell version
$restMethodParams = @{
Method = "POST"
Uri = "$NPSHost/api/v1/Credential"
Headers = $Headers
WebSession = $WebSession
Body = (ConvertTo-Json $CredentialPayload)
}
# Add SkipCertificateCheck parameter if PowerShell version supports it
if ($currentPSEdition -eq "Core" -or ($currentPSEdition -eq "Desktop" -and $currentPSVersion -ge 6)) {
$restMethodParams.Add("SkipCertificateCheck", $true)
} else {
# For older PowerShell versions, we need to use a callback to ignore certificate validation
Write-Host "Using compatible certificate validation for PowerShell Desktop" -ForegroundColor Yellow
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
# Set TLS 1.2
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
}
# Convert to JSON and make the POST request
try {
Write-Host "Creating credential in Netwrix Privilege Secure..." -ForegroundColor Yellow
$response = Invoke-RestMethod @restMethodParams
Write-Host "`nCredential created successfully!`n" -ForegroundColor Green
Write-Host "Credential details:" -ForegroundColor Cyan
$response
} catch {
Write-Error "Failed to create credential: $_"
if ($_.ErrorDetails.Message) {
Write-Error "Error details: $($_.ErrorDetails.Message)"
}
}
# After successfully creating the credential, capture the credentialID
$credentialId = $response.id
# Retrieve domains from API Endpoint!!!
try {
Write-Host "Retrieving available domains..." -ForegroundColor Yellow
$domainsResponse = Invoke-RestMethod -Method GET -Uri "$NPSHost/api/v1/ManagedResource/Search?skip=0&filterType=1" -Headers $Headers -WebSession $WebSession -SkipCertificateCheck
# Check if we have domains in the data array
if ($domainsResponse -and $domainsResponse.data -and $domainsResponse.data.Count -gt 0) {
$domains = $domainsResponse.data
Write-Host "Found $($domains.Count) domains" -ForegroundColor Cyan
Write-Host "Available domains:" -ForegroundColor Cyan
# Display domains with index numbers
for ($i = 0; $i -lt $domains.Count; $i++) {
Write-Host "[$i] $($domains[$i].name)" -ForegroundColor Cyan
}
# Add option for "None"
Write-Host "[N] None (No associated domain)" -ForegroundColor Cyan
# Prompt user to select a domain
$selection = Read-Host -Prompt "Enter the number of the domain to associate, or 'N' for none"
# Process the selection
if ($selection -eq "N" -or $selection -eq "n") {
Write-Host "No domain will be associated." -ForegroundColor Yellow
$associatedDomainId = $null
}
elseif ($selection -match '^\d+$' -and [int]$selection -ge 0 -and [int]$selection -lt $domains.Count) {
$selectedDomain = $domains[[int]$selection]
Write-Host "Selected domain: $($selectedDomain.name)" -ForegroundColor Green
$associatedDomainId = $selectedDomain.id
}
else {
Write-Host "Invalid selection. No domain will be associated." -ForegroundColor Yellow
$associatedDomainId = $null
}
} else {
Write-Host "No domains available." -ForegroundColor Yellow
$associatedDomainId = $null
}
} catch {
Write-Host "Error retrieving domains: $($_.Exception.Message)" -ForegroundColor Red
$associatedDomainId = $null
}
# Now create the Azure AD Tenant payload with the associatedDomainId (if selected)
$azureAdTenantPayload = @{
name = "EntraID - $initialDomain"
tenantId = $tenantId
logonUrl = "https://portal.azure.com"
emailDomain = $initialDomain
processGroupMembership = $false
serviceAccountId = $credentialId
platformId = "319034e0-f7eb-4261-8624-c55a086528fc"
}
# Only add associatedDomainId if a domain was selected
if ($associatedDomainId) {
$azureAdTenantPayload.Add("associatedDomainId", $associatedDomainId)
}
# Convert to JSON and continue with the Azure AD Tenant creation
$jsonPayload = ConvertTo-Json $azureAdTenantPayload -Depth 5
# Create the Azure AD Tenant resource
try {
Write-Host "Creating EntraID resource in Netwrix Privilege Secure..." -ForegroundColor Yellow
$tenantResponse = Invoke-RestMethod -Method POST -Uri "$NPSHost/api/v1/azureAdTenant/Create" -Headers $Headers -WebSession $WebSession -Body $jsonPayload -ContentType "application/json" -SkipCertificateCheck
Write-Host "`nEntraID resource created successfully!`n" -ForegroundColor Green
Write-Host "EntraID resource details:" -ForegroundColor Cyan
$tenantResponse | Format-List
} catch {
Write-Host "Failed to create Azure AD Tenant resource: $($_.Exception.Message)" -ForegroundColor Red
}