Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 1.9.0

### Added

- **Device Notes**: `Get-NCDeviceNotes`, `New-NCDeviceNote`, `Set-NCDeviceNote`,
`Remove-NCDeviceNote` — full CRUD for device notes including bulk operations.
- **Custom PSA Tickets**: `New-NCCustomPsaTicket` (create), `Invoke-NCCustomPsaTicket`
(reopen/resolve). `Get-NCCustomPsaTicket` now supports the credential-free `GET`
variant in addition to the existing `POST`.
- **Standard PSA**: `Set-NCStandardPsaCustomerMapping` (update mappings),
`Get-NCStandardPsaCompanies`, `Get-NCStandardPsaContacts`, `Get-NCStandardPsaSites`.
- **Remote Control**: `New-NCRemoteControlTask`, `Get-NCRemoteControlType`.
- **Org Unit Limits**: `Get-NCOrgLimits`, `Set-NCOrgLimits`.
- **User Management**: `New-NCUser` (create user in org unit),
`Get-NCCurrentUser` (`GET /api/users/me`).
- **Windows Services**: `Invoke-NCDeviceServiceAction` (start/stop/restart).
- **SSO Authentication**: `Connect-NCentral -SsoToken` and `Set-NCRestConfig -SsoToken`
for identity-provider-based authentication via `POST /api/auth/sso`.
- `Get-NCServerInfo -Time`: `GET /api/server-info/time`.
- `Get-NCApiLinks`: added `-AccessGroups`, `-ScheduledTasks`, `-Users` switches
for the remaining navigation endpoints.

## 1.8.1

### Fixed
Expand Down
19 changes: 18 additions & 1 deletion NCRestAPI.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
RootModule = 'NCRestAPI.psm1'

# Version number of this module.
ModuleVersion = '1.8.1'
ModuleVersion = '1.9.0'

# Supported PSEditions
CompatiblePSEditions = @('Desktop', 'Core')
Expand Down Expand Up @@ -74,22 +74,26 @@
'Get-NCActiveIssues',
'Get-NCApplianceTask',
'Get-NCAssetLifecycle',
'Get-NCCurrentUser',
'Get-NCCustomers',
'Get-NCCustomPsaTicket',
'Get-NCDefaultDeviceProperty',
'Get-NCDefaultOrgProperty',
'Get-NCDeviceActivationKey',
'Get-NCDeviceAssets',
'Get-NCDeviceMaintenanceWindows',
'Get-NCDeviceNotes',
'Get-NCDeviceProperty',
'Get-NCDeviceScheduledTasks',
'Get-NCDeviceServices',
'Get-NCDevices',
'Get-NCFilters',
'Get-NCJobStatus',
'Get-NCOrgLimits',
'Get-NCOrgProperty',
'Get-NCOrgUnits',
'Get-NCRegTokens',
'Get-NCRemoteControlType',
'Get-NCReport',
'Get-NCRestApiInfo',
'Get-NCRestData',
Expand All @@ -99,28 +103,41 @@
'Get-NCServiceOrgs',
'Get-NCSites',
'Get-NCSoftwareInstallers',
'Get-NCStandardPsaCompanies',
'Get-NCStandardPsaContacts',
'Get-NCStandardPsaCustomerMapping',
'Get-NCStandardPsaSites',
'Get-NCUserRoles',
'Get-NCUsers',
'Invoke-NCCustomPsaTicket',
'Invoke-NCDeviceServiceAction',
'New-NCCustomer',
'New-NCCustomPsaTicket',
'New-NCDevice',
'New-NCDeviceAccessGroup',
'New-NCDeviceNote',
'New-NCMaintenanceWindows',
'New-NCOrgAccessGroup',
'New-NCPatchComparisonReport',
'New-NCRemoteControlTask',
'New-NCScheduledTask',
'New-NCServiceOrg',
'New-NCSite',
'New-NCSoftwareDownloadLink',
'New-NCUser',
'New-NCUserRole',
'Remove-NCDevice',
'Remove-NCDeviceNote',
'Remove-NCMaintenanceWindows',
'Set-NCAssetLifecycle',
'Set-NCDefaultOrgProperty',
'Set-NCDeviceNote',
'Set-NCDeviceProperty',
'Set-NCMaintenanceWindows',
'Set-NCOrgLimits',
'Set-NCOrgProperty',
'Set-NCRestConfig',
'Set-NCStandardPsaCustomerMapping',
'Test-NCStandardPsaCredential',
'Update-NCAssetLifecycle'
)
Expand Down
42 changes: 40 additions & 2 deletions Private/NCRestAPI.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class NCRestAPI {
# big -All pulls or pipeline fan-out against rate-limited tenants.
[int]$ThrottleMs = 0
[bool]$Verbose
hidden [bool]$UseSso = $false
hidden [bool]$InPager = $false
hidden [datetime]$LastRequestAt = [datetime]::MinValue

Expand All @@ -38,6 +39,16 @@ class NCRestAPI {
$this.Authenticate()
}

NCRestAPI([string]$baseUrl, [securestring]$ssoToken, [string]$accessTokenExpiration, [string]$refreshTokenExpiration, [bool]$verbose, [bool]$useSso) {
$this.BaseUrl = $baseUrl
$this.ApiToken = $ssoToken
$this.AccessTokenExpiration = $accessTokenExpiration
$this.RefreshTokenExpiration = $refreshTokenExpiration
$this.Verbose = $verbose
$this.UseSso = $useSso
if ($useSso) { $this.AuthenticateSso() } else { $this.Authenticate() }
}

static [securestring] ToSecureString([string]$plain) {
$s = New-Object System.Security.SecureString
foreach ($c in $plain.ToCharArray()) { $s.AppendChar($c) }
Expand Down Expand Up @@ -93,6 +104,30 @@ class NCRestAPI {
$this.Log("[NCRESTAPI] Authenticate: succeeded.")
}

[void] AuthenticateSso() {
$this.Log("[NCRESTAPI] AuthenticateSso: starting.")
$url = "$($this.BaseUrl)/api/auth/sso"
$headers = @{
'Accept' = '*/*'
'Authorization' = "Bearer $($this.Reveal($this.ApiToken))"
}
if ($this.RefreshTokenExpiration) { $headers['X-REFRESH-EXPIRY-OVERRIDE'] = $this.RefreshTokenExpiration }
if ($this.AccessTokenExpiration) { $headers['X-ACCESS-EXPIRY-OVERRIDE'] = $this.AccessTokenExpiration }

try {
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Post -TimeoutSec $this.TimeoutSec
} catch {
$this.Log("[NCRESTAPI] AuthenticateSso: failed: $($_.Exception.Message)")
throw "[NCRESTAPI] SSO authentication failed: $($_.Exception.Message)"
}
if (-not $response.tokens.access.token -or -not $response.tokens.refresh.token) {
throw "[NCRESTAPI] AuthenticateSso: response missing tokens."
}
$this.AccessToken = [NCRestAPI]::ToSecureString($response.tokens.access.token)
$this.RefreshToken = [NCRestAPI]::ToSecureString($response.tokens.refresh.token)
$this.Log("[NCRESTAPI] AuthenticateSso: succeeded.")
}

[bool] ValidateToken() {
if (-not $this.AccessToken) { return $false }
$url = "$($this.BaseUrl)/api/auth/validate"
Expand All @@ -119,7 +154,7 @@ class NCRestAPI {
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $refreshPlain -TimeoutSec $this.TimeoutSec
} catch {
$this.Log("[NCRESTAPI] RefreshAccessToken: $($_.Exception.Message). Re-authenticating.")
$this.Authenticate()
if ($this.UseSso) { $this.AuthenticateSso() } else { $this.Authenticate() }
return
}
if (-not $response.tokens.access.token) {
Expand All @@ -132,7 +167,10 @@ class NCRestAPI {
}

[void] EnsureValidToken() {
if (-not $this.AccessToken) { $this.Authenticate(); return }
if (-not $this.AccessToken) {
if ($this.UseSso) { $this.AuthenticateSso() } else { $this.Authenticate() }
return
}
if (-not $this.ValidateToken()) { $this.RefreshAccessToken() }
}

Expand Down
17 changes: 12 additions & 5 deletions Public/Connect-NCentral.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,48 @@ Establishes a connection to an N-central server.

.DESCRIPTION
Wrapper around Set-NCRestConfig that follows the PowerShell Connect-* convention.
Supports both API token and SSO token authentication.

.PARAMETER BaseUrl
Fully-qualified URL of the N-central server (https:// scheme added if omitted).

.PARAMETER ApiToken
User-level API token. Accepts [string] or [securestring].

.PARAMETER SsoToken
SSO access token from an external identity provider. Accepts [string] or [securestring].

.PARAMETER AccessTokenExpiration
Override access-token lifetime (e.g. '1h', '120s'). Default '1h'.

.PARAMETER RefreshTokenExpiration
Override refresh-token lifetime (e.g. '25h'). Default '25h'.

.PARAMETER PassThru
Emit the connection info object after authenticating - useful in scripts that want to
confirm the connect succeeded without a follow-up call.
Emit the connection info object after authenticating.

.EXAMPLE
Connect-NCentral -BaseUrl 'n-central.example.com' -ApiToken $token

.EXAMPLE
$conn = Connect-NCentral -BaseUrl 'n-central.example.com' -ApiToken $token -PassThru
Connect-NCentral -BaseUrl 'n-central.example.com' -SsoToken $ssoToken -PassThru
#>
function Connect-NCentral {
[CmdletBinding()]
[CmdletBinding(DefaultParameterSetName = 'ApiToken')]
[OutputType([pscustomobject])]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$BaseUrl,

[Parameter(Mandatory)]
[Parameter(Mandatory, ParameterSetName = 'ApiToken')]
[ValidateNotNullOrEmpty()]
[object]$ApiToken,

[Parameter(Mandatory, ParameterSetName = 'SsoToken')]
[ValidateNotNullOrEmpty()]
[object]$SsoToken,

[ValidatePattern('^\d+[smh]$')]
[string]$AccessTokenExpiration = '1h',

Expand Down
14 changes: 12 additions & 2 deletions Public/Get-NCApiLinks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
Returns the list of discoverable endpoints under one of the root hypermedia nodes.

.DESCRIPTION
Covers three `_links` navigation endpoints:
Covers six `_links` navigation endpoints:

- default -> GET /api (top-level endpoint catalogue)
- -AccessGroups -> GET /api/access-groups
- -CustomPsa -> GET /api/custom-psa
- -CustomPsaTickets -> GET /api/custom-psa/tickets
- -ScheduledTasks -> GET /api/scheduled-tasks
- -StandardPsa -> GET /api/standard-psa
- -Users -> GET /api/users

Prefer `Get-NCServerInfo` for root `/api` metadata. This cmdlet exists so the module
has explicit coverage for every spec endpoint.
Expand All @@ -24,17 +27,24 @@ function Get-NCApiLinks {
[OutputType([pscustomobject])]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Parameters are discriminators consumed via ParameterSetName.')]
param (
[Parameter(ParameterSetName = 'Root')][switch]$Root,
[Parameter(ParameterSetName = 'AccessGroups')][switch]$AccessGroups,
[Parameter(ParameterSetName = 'CustomPsa')][switch]$CustomPsa,
Comment thread
theonlytruebigmac marked this conversation as resolved.
[Parameter(ParameterSetName = 'CustomPsaTickets')][switch]$CustomPsaTickets,
[Parameter(ParameterSetName = 'StandardPsa')][switch]$StandardPsa
[Parameter(ParameterSetName = 'ScheduledTasks')][switch]$ScheduledTasks,
[Parameter(ParameterSetName = 'StandardPsa')][switch]$StandardPsa,
[Parameter(ParameterSetName = 'Users')][switch]$Users
)

Write-Verbose "[FUNCTION] Get-NCApiLinks: invoked."
$api = Get-NCRestApiInstance
$endpoint = switch ($PSCmdlet.ParameterSetName) {
'AccessGroups' { 'api/access-groups' }
'CustomPsa' { 'api/custom-psa' }
'CustomPsaTickets' { 'api/custom-psa/tickets' }
'ScheduledTasks' { 'api/scheduled-tasks' }
'StandardPsa' { 'api/standard-psa' }
'Users' { 'api/users' }
default { 'api' }
}
$api.Get($endpoint)
Expand Down
19 changes: 19 additions & 0 deletions Public/Get-NCCurrentUser.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<#
.SYNOPSIS
Retrieves the current authenticated user's information.

.DESCRIPTION
GET /api/users/me.

.EXAMPLE
Get-NCCurrentUser
#>
function Get-NCCurrentUser {
[CmdletBinding()]
[OutputType([pscustomobject])]
param ()

Write-Verbose "[FUNCTION] Get-NCCurrentUser: api/users/me"
$api = Get-NCRestApiInstance
$api.Get('api/users/me')
}
24 changes: 15 additions & 9 deletions Public/Get-NCCustomPsaTicket.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
Retrieves details for a specific Custom-PSA ticket.

.DESCRIPTION
POST /api/custom-psa/tickets/{customPsaTicketId}. The spec defines this as POST with a
PSA credential body.
GET /api/custom-psa/tickets/{customPsaTicketId} returns the ticket without credentials.
Supply -Credential to use the POST variant which authenticates against the PSA integration.

.PARAMETER CustomPsaTicketId
Ticket ID.

.PARAMETER Credential
PSCredential containing username and password for the PSA integration.
Optional PSCredential for the PSA integration. When omitted, the credential-free GET endpoint is used.

.EXAMPLE
Get-NCCustomPsaTicket -CustomPsaTicketId 'TKT-42'

.EXAMPLE
Get-NCCustomPsaTicket -CustomPsaTicketId 'TKT-42' -Credential (Get-Credential)
Expand All @@ -23,16 +26,19 @@ function Get-NCCustomPsaTicket {
[ValidateNotNullOrEmpty()]
[string]$CustomPsaTicketId,

[Parameter(Mandatory)]
[pscredential]$Credential
)
begin { $api = Get-NCRestApiInstance }
process {
Write-Verbose "[FUNCTION] Get-NCCustomPsaTicket: invoked."
$body = @{
username = $Credential.UserName
password = $Credential.GetNetworkCredential().Password
if ($Credential) {
Write-Verbose "[FUNCTION] Get-NCCustomPsaTicket: POST api/custom-psa/tickets/$CustomPsaTicketId"
$body = @{
username = $Credential.UserName
password = $Credential.GetNetworkCredential().Password
}
return $api.Post("api/custom-psa/tickets/$CustomPsaTicketId", $body)
}
$api.Post("api/custom-psa/tickets/$CustomPsaTicketId", $body)
Write-Verbose "[FUNCTION] Get-NCCustomPsaTicket: GET api/custom-psa/tickets/$CustomPsaTicketId"
$api.Get("api/custom-psa/tickets/$CustomPsaTicketId")
}
}
49 changes: 49 additions & 0 deletions Public/Get-NCDeviceNotes.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<#
.SYNOPSIS
Retrieves notes for a device.

.DESCRIPTION
GET /api/devices/{deviceId}/notes. Supports pagination and pipeline input.

.PARAMETER DeviceId
Target device.

.EXAMPLE
Get-NCDeviceNotes -DeviceId 987 -All
#>
function Get-NCDeviceNotes {
[CmdletBinding(DefaultParameterSetName = 'Page')]
[OutputType([pscustomobject])]
param (
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateNotNullOrEmpty()]
[string]$DeviceId,

[Parameter(ParameterSetName = 'All')]
[switch]$All,

[Parameter(ParameterSetName = 'Page')]
[int]$PageNumber,

[Parameter(ParameterSetName = 'Page')]
[int]$PageSize
)

begin { $api = Get-NCRestApiInstance }

process {
$endpoint = "api/devices/$DeviceId/notes"

if ($All) {
return Invoke-NCPagedRequest -Endpoint $endpoint
}

$queryParameters = @{}
if ($PageNumber) { $queryParameters['pageNumber'] = $PageNumber }
if ($PageSize) { $queryParameters['pageSize'] = $PageSize } else { $queryParameters['pageSize'] = 500 }

$endpoint += ConvertTo-NCQueryString -Parameters $queryParameters
Write-Verbose "[FUNCTION] Get-NCDeviceNotes: $endpoint"
$api.Get($endpoint)
}
}
Loading