Using the API to Create Activity Sessions in Privilege Secure

Activity Sessions

Create an activity session

The following uses just PowerShell (not the NPS PowerShell module) to create an activity session. If you are creating a Resource based activity session, then all you need is the Resource Name and the Activity Name!

The GitHub documentation shows ALL the fields, but the simplest use case is the following.

The following PowerShell is using PowerShell7 to make it compatible with PowerShell 5, you need to remove the -SkipCertificateCheck parameter from the Invoke-RestMethod

https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows

# PowerShell example
param(
    $NPSUrl = "https://localhost:6500",
    $Login = "domain\user",
    $Password = "Password",
    $MfaCode = "123456",
    [Parameter(Mandatory)][String]$ActivityName,
    [Parameter(Mandatory)][String]$ResourceName
)
$Login = @{
    Login = $Login
    Password = $Password
}
# Cookie container for multi-factor authentication
$WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$Token = Invoke-RestMethod -Uri "$($NPSUrl)/signinBody" -Method POST -Body (ConvertTo-Json $Login) -WebSession $WebSession -ContentType "application/json" -SkipCertificateCheck
$Token = Invoke-RestMethod -Uri "$($NPSUrl)/signin2fa" -Method Post -Body $MfaCode -Headers @{Authorization = "Bearer $Token"} -WebSession $WebSession -ContentType "application/json" -SkipCertificateCheck

$Headers = @{
    Authorization = "Bearer $Token"
}

$Payload = @{
    ManagedResourceName = $ResourceName
    ActivityName = $ActivityName
}
$JsonBody = ConvertTo-Json $Payload
Write-Host "$($JsonBody)"

Invoke-RestMethod -Method POST -Uri "$($NPSUrl)/api/v1/ActivitySession" -Body $JsonBody -Headers $Headers -ContentType "application/json" -SkipCertificateCheck

The POST to the ActivitySession will return the ActivitySession object that was created if you have a policy for your login that allows you to create the session.

NOTE: We send back a lot of data

Example output:

id                        : b9fc22e3-7f30-4f81-b3cf-b17e5fcfd76c
createdBy                 : baf63bda-5971-4774-8e56-d2c2cb085328
createdByUserName         : RTEST\kevin.horvatin
createdFromAddress        : 10.34.29.207
createdDateTimeUtc        : 3/11/2025 6:39:30 PM
credentialId              : 4a711f90-af39-4755-b7d5-f0e796f845d1
userCredentialId          : ecd3d598-5b92-4038-a8bf-c0314c0e3952
connectCredentialId       : 
locked                    : False
loginAccountName          : kevin.horvatin-nps
vaultId                   : 
vaultInfo                 : 
activityId                : df059c1b-47f4-438a-92ab-f54201fc95b8
activity                  : @{latestSessionActualStartUtc=; id=df059c1b-47f4-438a-92ab-f54201fc95b8; createdBy=baf63bda-5971-4774-8e56-d2c2cb085328; 
                            modifiedBy=baf63bda-5971-4774-8e56-d2c2cb085328; name=Connect as Managed; description=; activityConfigurationId=; 
                            activityConfiguration=; platformId=43a54a6d-1ba3-4b98-a2eb-552e03c60766; platform=; 
                            startActionGroupId=a3fd42a0-fd85-48d6-8888-7f375de84d0a; duringActionGroupId=0e52b0d8-94c6-4a57-ac88-0fd74e6fda01; 
                            endActionGroupId=a263e953-2eb9-4660-bd33-1632e57eaad9; activityType=0; loginAccount=1; 
                            loginAccountNameFormat=%samaccountname%-nps; requesterLoginFormat=0; applicationToLaunch=; preferredRDSHostId=; 
                            connectCredentialId=; createAccount=True; activityGroupActivities=System.Object[]; deleteAccount=False; vaultId=; vaultInfo=; 
                            logonUrl=; isDefault=False; isDeleted=False; isUserModified=False; nodeId=e80a7deb-ee2a-4410-9809-10760746559d; 
                            createdDateTimeUtc=2/14/2025 9:17:43 PM; modifiedDateTimeUtc=2/14/2025 9:17:43 PM}
activityConfigurationId   : dbf44018-8de6-4fdd-9f00-1b641477e58b
activityConfiguration     : @{id=dbf44018-8de6-4fdd-9f00-1b641477e58b; name=Default; description=Default connection profile.; type=0; 
                            createdBy=baf63bda-5971-4774-8e56-d2c2cb085328; modifiedBy=baf63bda-5971-4774-8e56-d2c2cb085328; createdDateTimeUtc=3/11/2025 
                            6:39:28 PM; modifiedDateTimeUtc=3/11/2025 6:39:28 PM; isDefault=False; isDeleted=False; isUserModified=False; 
                            nodeId=e80a7deb-ee2a-4410-9809-10760746559d; activityConfigurationSettings=System.Object[]; customFields=System.Object[]; 
                            deleteAccount=False; sessionRetryInterval=; approvedWorkflowEmailTemplateId=; notifyApproversWorkflowEmailTemplateId=; 
                            maxSessionLength=1200; allowSessionExtension=False; sessionExtensionMinutes=30; sessionExtensionCount=2; 
                            sessionMonitorInterval=5; expirationTimeoutThreshold=5; rdpProxyHost=; sshProxyHost=; sshScanDc=False; recordAudio=False; 
                            proxyAutoConnect=True; record=True; approvalTypeRequired=1; approvalWorkflowId=d320f1f0-9499-4241-b1dc-1cf45a993d76; 
                            approvalWorkflowEmailTemplateId=18376f6c-5cc0-4410-b181-1f067e2e699c; monitorEntireSession=True; allowViewPassword=False; 
                            allowPasswordAccess=True; allowAutofillPassword=True; leaveInGroup=True; activityTokenComplexity=; 
                            clearWebsiteDataAfterStop=False; clearWebsiteDataBeforeStart=False; notesRequired=False; ticketRequired=False; 
                            viewPasswordInSeconds=}
activitySessionGroupId    : 
activitySessionGroup      : 
accessControlPolicyId     : 57bcb5e7-f0a5-400f-8d7c-bd40b6db13ad
accessControlPolicy       : @{id=57bcb5e7-f0a5-400f-8d7c-bd40b6db13ad; name=Linux Activities; description=; 
                            activityConfigurationId=1f73121f-c34b-49dc-a905-5c9e6a1b50db; activityConfiguration=; priority=0; isDisabled=False; 
                            isDeleted=False; isDefault=False; isUserModified=False; managedAccountPolicyJoin=System.Object[]; 
                            managedAccountGroupPolicyJoin=System.Object[]; managedResourcePolicyJoin=System.Object[]; 
                            managedResourceGroupPolicyJoin=System.Object[]; activityJoin=System.Object[]; activityGroupJoin=System.Object[]; 
                            credentialPolicyJoin=System.Object[]; credentialGroupPolicyJoin=System.Object[]; 
                            userAndGroupCollectionPolicyJoin=System.Object[]; policyType=0; nodeId=e80a7deb-ee2a-4410-9809-10760746559d; 
                            createdDateTimeUtc=2/14/2025 7:48:09 PM; modifiedDateTimeUtc=2/14/2025 7:48:09 PM}
managedAccountId          : e69e4d0b-eb0f-43ae-8206-1efb52c9a925
managedAccount            : @{id=e69e4d0b-eb0f-43ae-8206-1efb52c9a925; name=Kevin Horvatin; type=0; locked=False; 
                            userId=052cf429-4e88-4e74-84d2-be5b20bd93dd; managedAccountJoin=System.Object[]; managedAccountPolicyJoin=System.Object[]; 
                            sid=S-1-5-21-1366766991-2637077591-3940904154-675131; userCollectionJoin=System.Object[]; isReviewer=False; 
                            nodeId=e80a7deb-ee2a-4410-9809-10760746559d; createdDateTimeUtc=10/23/2024 2:44:36 PM; modifiedDateTimeUtc=10/23/2024 
                            2:44:36 PM}
managedResourceId         : 2af4aabb-f680-4b87-b8cb-a6aed15e02d2
managedResource           : @{id=2af4aabb-f680-4b87-b8cb-a6aed15e02d2; name=10.61.50.84; type=0; hostId=d5bb6a89-381c-4308-abf6-430e8c5ba794; host=; 
                            hostScanHostId=95e25f1c-8e5d-4079-a428-3b7c8ee1c282; hostScanHost=; domainConfigId=; websiteId=; website=; azureAdTenantId=; 
                            azureAdTenant=; secretVaultId=; secretVault=; managedDatabaseId=; managedDatabase=; 
                            platformId=43a54a6d-1ba3-4b98-a2eb-552e03c60766; platform=; displayName=; ipAddress=; 
                            serviceAccountId=4a711f90-af39-4755-b7d5-f0e796f845d1; serviceAccount=; manageAccount=0; protectedGroup=System.Object[]; 
                            activityConfigurationId=; activityConfiguration=; actionQueueId=; actionQueue=; managedResourceJoin=System.Object[]; 
                            managedResourcePolicyJoin=System.Object[]; manageResourceProtectionPolicyJoin=System.Object[]; verificationScheduleId=; 
                            verificationSchedule=; passwordComplexityPolicyId=; passwordComplexityPolicy=; portSsh=22; portRdp=3389; portWinRm=5985; 
                            portWinRmHttps=5986; winRmHttpSetting=-1; disableWinRm=False; acceptThumbprintOnFirstDiscovery=True; 
                            trustedThumbprint=iGI7j1r/hY+v1qkN9Or+nTzFYRRwsW2Fi70jFF+XnaY; 
                            discoveredThumbprint=iGI7j1r/hY+v1qkN9Or+nTzFYRRwsW2Fi70jFF+XnaY; sshTrustActionType=1; certificateType=2; 
                            nodeId=e80a7deb-ee2a-4410-9809-10760746559d; createdDateTimeUtc=2/14/2025 7:46:32 PM; modifiedDateTimeUtc=2/14/2025 7:46:28 AM}
sessionInitActionQueueId  : 
sessionInitActionQueue    : 
createUserActionQueueId   : 79b95785-4e72-486b-843e-0c2ce15894a7
createUserActionQueue     : 
disableUserActionQueueId  : 0fe1d4d6-a0b6-4f31-8286-fc2305193ae4
disableUserActionQueue    : 
startActionQueueId        : d72dc34e-0cfa-4dca-b586-61b457d3dc19
startActionQueue          : 
duringActionQueueId       : 8a63de21-b126-4e5d-9ecd-530a0e683d9b
duringActionQueue         : 
monitorActionQueueId      : 
monitorActionQueue        : 
endActionQueueId          : 3471091c-9688-4c7f-9c4f-d14d953f64a6
endActionQueue            : 
messageActionQueueId      : c8516e38-a4ac-4b07-bf3b-963ec216ea3c
messageActionQueue        : 
status                    : 0
statusDescription         : Activity session created.
scheduledStartDateTimeUtc : 3/11/2025 6:39:30 PM
actualStartDateTimeUtc    : 
scheduledEndDateTimeUtc   : 3/12/2025 2:39:30 PM
actualEndDateTimeUtc      : 
cancelledBy               : 
targetUserId              : 514fb219-e0b8-4efe-a357-dfb7721b8a0c
targetId                  : d5bb6a89-381c-4308-abf6-430e8c5ba794
proxySessions             : {}
approvalWorkflowId        : 
approvalWorkflow          : 
approvalWorkflowApprovals : 
note                      : 
ticket                    : 
nodeId                    : e80a7deb-ee2a-4410-9809-10760746559d
modifiedDateTimeUtc       : 3/11/2025 6:39:30 PM
allowSessionExtension     : False
sessionExtensionCount     : 2
sessionExtensionMinutes   : 30
customFields              : {}

Monitoring the status

Now that you have started the session, can you check if it starts? SURE! let’s make a change to line 30

$activitySession = Invoke-RestMethod -Method POST -Uri "$($NPSUrl)/api/v1/ActivitySession" -Body $JsonBody -Headers $Headers -ContentType "application/json" -SkipCertificateCheck -ErrorAction Stop

Next let’s use the $activitySession variable and grab the status property from it and use that to fetch the status. We can continue to fetch the status every 10-15 seconds until the session is running or fails. (if it fails, we will add code to fetch the logs later).

Ok - so lets add the following to the bottom of our script

while ($activitySession.status -lt 1) {
   # Sleep for 15 seconds and check again, we want to
   # give the server some time to do its work, you
   # could sleep for shorter amount of time
   Write-Host "$($activitySession.status) $($activitySession.statusDescription)"
   Start-Sleep -Seconds 15
   # Refresh the session to get the latest status
   $activitySession = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/$($activitySession.id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
}
Write-Host "$($activitySession.status) $($activitySession.statusDescription)"

If everything worked according to plan, you should see just the things we are now sending out using Write-Host:

{
  "ManagedResourceName": "kah-terminal.rtest.com",
  "ActivityName": "Activity Token for Domain Admin Access"
}
0 Activity session created.
1 Activity session  started.

Can I connect to the session?

The answer is you can download the RDP file OR get the SSH link. After that, you can certainly “open” the RDP file and the registered client will open it. For SSH, if you have Windows and a registered SSH Url handler, you can Start it. Otherwise you might have to parse it and start Putty or ssh directly.

To get the connection information, you just need to call the right API endpoint!

The below is for Windows, we will show how to determine whether to fetch RDP or SSH in a later section

Let’s add the following to our script:

if ($null -ne $activitySession) {
    if ($activitySession.status -eq 1) {
       Write-Host "Fetching RDP file"
       $RDPContents = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/Rdp/$($activitySession.Id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
       if ($null -ne $RDPContents)
       {
          Write-Host "Writing RDP file as temp.RDP"
          $RDPContents | Set-Content -Path "temp.RDP"
       }
    }
    else
    {
       Write-Host "Session isn't running"
    }
}

What went wrong?

We can get the logs for the session by simply calling the ActivitySession/{id}/Log endpoint. After Write-Host "Session isn't running"

       Write-Host "Session isn't running"
       Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/$($activitySession.id)/Log" -Headers $Headers -ContentType "application/json" -ErrorAction Stop

But what if I want SSH???

On the ActivitySession object from above you might have noticed the ManagedResource object associated with the ActivitySession. It has a property that can help! We can use that and check on the PlatformId for the resource.

PlatformIds are GUIDs and those can make it difficult, but those GUIDs are fixed values, and you can base which endpoint to call to fetch the RDP file from.

NPS only returns RDP files for the following platforms:

  • Windows d07c4352-ea1a-44a2-8fe8-6f198ec1119f
  • Active Directory d6a07d9c-4b2e-4430-8c5b-401724dce933

So armed with knowledge we can update our script again, I am going to add a function so we can make the code a bit easier to read. Put this after the param() block line 10 will work

function Test-IsPlatformValidForRDP {
  param(
     $ActivitySession
  )
  
  $PlatformId = $ActivitySession.ManagedResource.PlatformId
  return (
     $PlatformId -eq "d07c4352-ea1a-44a2-8fe8-6f198ec1119f" -or # Windows
     $PlatformId -eq "d6a07d9c-4b2e-4430-8c5b-401724dce933")    # AD
}

Now that we have our function - let’s update the script!

       if (Test-IsPlatformValidForRDP -ActivitySession $activitySession)
       {
          Write-Host "Fetching RDP file"
          $RDPContents = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/Rdp/$($activitySession.Id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
          if ($null -ne $RDPContents)
          {
             Write-Host "Writing RDP file as temp.RDP"
             $RDPContents | Set-Content -Path "temp.RDP"
             ## If you wanted, you can use uncomment the line below to Automatically open the file in Windows
             ## Start ./temp.RDP ## Windows
             ## open ./temp.RDP  ## macOS
          }
       }
       else
       {
          $SSHUrl = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/Ssh/$($activitySession.Id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
          Write-Host "$SSHUrl"
       }

The entire script for reference

# PowerShell example
param(
    $NPSUrl = "https://localhost:6500",
    $Login = "domain\user",
    $Password = "Password",
    $MfaCode = "123456",
    [Parameter(Mandatory)][String]$ActivityName,
    [Parameter(Mandatory)][String]$ResourceName
)

function Test-IsPlatformValidForRDP {
  param(
     $ActivitySession
  )
  
  $PlatformId = $ActivitySession.ManagedResource.PlatformId
  return (
     $PlatformId -eq "d07c4352-ea1a-44a2-8fe8-6f198ec1119f" -or # Windows
     $PlatformId -eq "d6a07d9c-4b2e-4430-8c5b-401724dce933")    # AD
}

$Login = @{
    Login = $Login
    Password = $Password
}
# Cookie container for multi-factor authentication
$WebSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$Token = Invoke-RestMethod -Uri "$($NPSUrl)/signinBody" -Method POST -Body (ConvertTo-Json $Login) -WebSession $WebSession -ContentType "application/json" -SkipCertificateCheck
$Token = Invoke-RestMethod -Uri "$($NPSUrl)/signin2fa" -Method Post -Body $MfaCode -Headers @{Authorization = "Bearer $Token"} -WebSession $WebSession -ContentType "application/json" -SkipCertificateCheck

$Headers = @{
    Authorization = "Bearer $Token"
}

$Payload = @{
    ManagedResourceName = $ResourceName
    ActivityName = $ActivityName
}
$JsonBody = ConvertTo-Json $Payload
Write-Host "$($JsonBody)"

$activitySession = Invoke-RestMethod -Method POST -Uri "$($NPSUrl)/api/v1/ActivitySession" -Body $JsonBody -Headers $Headers -ContentType "application/json" -SkipCertificateCheck -ErrorAction Stop

while ($activitySession.status -lt 1) {
   # Sleep for 15 seconds and check again, we want to
   # give the server some time to do its work, you
   # could sleep for shorter amount of time
   Write-Host "$($activitySession.status) $($activitySession.statusDescription)"
   Start-Sleep -Seconds 15
   # Refresh the session to get the latest status
   $activitySession = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/$($activitySession.id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
}

Write-Host "$($activitySession.status) $($activitySession.statusDescription)"

if ($null -ne $activitySession) {
    if ($activitySession.status -eq 1) {
       if (Test-IsPlatformValidForRDP -ActivitySession $activitySession)
       {
          Write-Host "Fetching RDP file"
          $RDPContents = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/Rdp/$($activitySession.Id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
          if ($null -ne $RDPContents)
          {
             Write-Host "Writing RDP file as temp.RDP"
             $RDPContents | Set-Content -Path "temp.RDP"
             ## If you wanted, you can use uncomment the line below to Automatically open the file in Windows
             ## Start ./temp.RDP ## Windows
             ## open ./temp.RDP  ## macOS
          }
       }
       else
       {
          $SSHUrl = Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/Ssh/$($activitySession.Id)" -Headers $Headers -ContentType "application/json" -SkipCertificateCheck
          Write-Host "$SSHUrl"
       }
    }
    else
    {
       Write-Host "Session isn't running"
       Invoke-RestMethod -Method GET -Uri "$($NPSUrl)/api/v1/ActivitySession/$($activitySession.id)/Log" -Headers $Headers -ContentType "application/json" -ErrorAction Stop
    }
}

Summary of API usage

Links to the GitHub docs for each endpoint below.

POST /api/v1/ActivitySession (ActivitySession/CreateAsync)

GET /api/v1/ActivitySession/{sessionId} (ActivitySession/GetByIdAsync)

GET /api/v1/ActivitySession/Rdp/{sessionId} (ActivitySession/GetRdpFileAsync)

GET /api/v1/ActivitySession/Ssh/{sessionId} (ActivitySession/GetSshUrlAsync)

6 Likes

Well done Kevin, I will certainly forward this to some of our partners and end customers and encourage them to join our Netwrix Community

3 Likes