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 theInvoke-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)