NinjaRmmApi.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<#
This file is part of the NinjaRmmApi module.
This module is not affiliated with, endorsed by, or related to NinjaRMM, LLC.
 
NinjaRmmApi is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
 
NinjaRmmApi is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
 
You should have received a copy of the GNU Affero General Public License along
with NinjaRmmApi. If not, see <https://www.gnu.org/licenses/>.
#>


Function Set-NinjaRmmSecrets {
    [OutputType('void')]
    Param(
        [AllowNull()]
        [String] $AccessKeyId,

        [AllowNull()]
        [String] $SecretAccessKey
    )

    $env:NinjaRmmAccessKeyID     = $AccessKeyId
    $env:NinjaRmmSecretAccessKey = $SecretAccessKey
}

Function Reset-NinjaRmmSecrets {
    [Alias('Remove-NinjaRmmSecrets')]
    [OutputType('void')]
    Param()

    Remove-Variable -Name $env:NinjaRmmAccessKeyID
    Remove-Variable -Name $env:NinjaRmmSecretAccessKey
}

Function Set-NinjaRmmServerLocation {
    [OutputType('void')]
    Param(
        [ValidateSet('US', 'EU')]
        [String] $Location = 'US'
    )

    $env:NinjaRmmServerLocation = $Location
}

Function Send-NinjaRmmApi {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String] $RequestToSend,

        [ValidateSet('GET', 'PUT', 'POST', 'DELETE')]
        [String] $Method = 'GET'
    )

    # Stop if our secrets have not been learned.
    If ($null -eq $env:NinjaRmmSecretAccessKey) {
        Throw [Data.NoNullAllowedException]::new('No secret access key has been provided. Please run Set-NinjaRmmSecrets.')
    }
    If ($null -eq $env:NinjaRmmAccessKeyID) {
        Throw [Data.NoNullAllowedException]::new('No access key ID has been provided. Please run Set-NinjaRmmSecrets.')
    }

    # Get the current date. Calling -Format converts it to a [String], so we
    # need two separate calls to Get-Date.
    $DateString = Get-Date -Format 'R' -Date ((Get-Date).ToUniversalTime())
    
    # Format our signing string correctly.
    # NinjaRMM's signature has a place to put Content-MD5 and Content-Type
    # values, but leaving them out ($null) seems to be perfectly acceptable.
    $ContentMD5   = $null
    $ContentType  = $null
    $StringToSign = @($Method, $ContentMD5, $ContentType, $DateString, $RequestToSend) -Join "`n"

    # Convert your string to a byte array, and then Base64-encode it.
    # Not sure why we're removing newlines, but Ninja's example C# code did it.
    $StringToSignBytes = [Text.Encoding]::UTF8.GetBytes($StringToSign)
    $EncodedString = ([Convert]::ToBase64String($StringToSignBytes)).Trim()

    # Construct our HMAC-SHA1 thing, then convert *it* to Base64 as well.
    # Sample: https://stackoverflow.com/questions/42150420/why-does-encrypting-hmac-sha1-in-exactly-the-same-code-in-c-sharp-and-powershell
    $Hasher = [Security.Cryptography.KeyedHashAlgorithm]::Create('HMACSHA1')
    $Hasher.Key = [Text.Encoding]::UTF8.GetBytes($env:NinjaRmmSecretAccessKey)
    $HashedStringBytes= $Hasher.ComputeHash([Text.Encoding]::UTF8.GetBytes($EncodedString))

    # Convert the result to a Base64 string.
    $Signature = [Convert]::ToBase64String($HashedStringBytes) -Replace "`n",""
    
    # Pick our server. By default, we will use the United States server.
    # However, the European Union server can be used instead.'
    If (($env:NinjaRmmServerLocation -eq 'US') -or ($null -eq $env:NinjaRmmServerLocation)) {
        $HostName = 'api.ninjarmm.com'
    }
    ElseIf ($env:NinjaRmmServerLocation -eq 'EU') {
        $HostName = 'eu-api.ninjarmm.com'
    }
    Else {
        Throw [ArgumentException]::new("The server location ${env:NinjaRmmServerLocation} is not valid. Please run Set-NinjaRmmServerLocation.")
    }

    # Create a user agent for logging purposes.
    $UserAgent  = "PowerShell/$($PSVersionTable.PSVersion) "
    $UserAgent += "NinjaRmmApi/$((Get-Module -Name 'NinjaRmmApi').Version) "
    $UserAgent += '(implementing API version 0.1.2)'

    # Ensure that TLS 1.2 is enabled, so that we can communicate with NinjaRMM.
    # It may be disabled by default before PowerShell 6.
    [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

    # Some new versions of PowerShell also support TLS 1.3. If that is a valid
    # option, then enable that, too, in case NinjaRMM ever enables it.
    If ([Net.SecurityProtocolType].GetMembers() -Contains 'Tls13') {
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls13
    }

    # Finally, send it.
    Write-Debug -Message ("Will send the request:`n`n" `
        + "$Method $RequestToSend HTTP/1.1`n" `
        + "Host: $HostName`n" `
        + "Authorization: NJ ${env:NinjaRmmAccessKeyID}:$Signature`n" `
        + "Date: $DateString`n" `
        + "User-Agent: $UserAgent")

    $Arguments = @{
        'Method'  = $Method
        'Uri'     = "https://$HostName$RequestToSend"
        'Headers' = @{
            'Authorization' = "NJ ${env:NinjaRmmAccessKeyID}:$Signature"
            'Date'          = $DateString
            'Host'          = $HostName
            'User-Agent'    = $UserAgent
        }
    }

    Return (Invoke-RestMethod @Arguments)
}

Function Get-NinjaRmmAlerts {
    [CmdletBinding(DefaultParameterSetName='AllAlerts')]
    Param(
        [Parameter(ParameterSetName='OneAlert')]
        [UInt32] $AlertId,

        [Parameter(ParameterSetName='AllAlertsSince')]
        [UInt32] $Since
    )

    $Request = '/v1/alerts'
    If ($PSCmdlet.ParameterSetName -eq 'OneAlert') {
        $Request += "/$AlertId"
    }
    ElseIf ($PSCmdlet.ParameterSetName -eq 'AllAlertsSince') {
        $Request += "/since/$Since"
    }

    Return (Send-NinjaRmmApi -RequestToSend $Request)
}

Function Reset-NinjaRmmAlert {
    [CmdletBinding()]
    [Alias('Remove-NinjaRmmAlert')]
    Param(
        [Parameter(Mandatory)]
        [UInt32] $AlertId
    )

    Return (Send-NinjaRmmApi -Method 'DELETE' -RequestToSend "/v1/alerts/$AlertId")
}

Function Get-NinjaRmmCustomers {
    [CmdletBinding(DefaultParameterSetName='AllCustomers')]
    Param(
        [Parameter(ParameterSetName='OneCustomer')]
        [UInt32] $CustomerId
    )

    $Request = '/v1/customers'
    If ($PSCmdlet.ParameterSetName -eq 'OneCustomer') {
        $Request += "/$CustomerId"
    }
    Return (Send-NinjaRmmApi -RequestToSend $Request)
}

Function Get-NinjaRmmDevices {
    [CmdletBinding(DefaultParameterSetName='AllDevices')]
    Param(
        [Parameter(ParameterSetName='OneDevice')]
        [UInt32] $DeviceId
    )

    $Request = '/v1/devices'
    If ($PSCmdlet.ParameterSetName -eq 'OneDevice') {
        $Request += "/$DeviceId"
    }
    Return (Send-NinjaRmmApi -RequestToSend $Request)
}