Powershell 7 script to create and provide everything needed in EntraID and sets up Resource in NPS-AM

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
}


4 Likes