DefenderAPI.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\DefenderAPI.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName DefenderAPI.Import.DoDotSource -Fallback $false if ($DefenderAPI_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName DefenderAPI.Import.IndividualFiles -Fallback $false if ($DefenderAPI_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath if ($doDotSource) { . $resolvedPath } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) { . Import-ModuleFile -Path $path } # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) { . Import-ModuleFile -Path $path } # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code <# This file loads the strings documents from the respective language folders. This allows localizing messages and errors. Load psd1 language files for each language you wish to support. Partial translations are acceptable - when missing a current language message, it will fallback to English or another available language. #> Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'DefenderAPI' -Language 'en-US' class DefenderToken { #region Token Data [string]$AccessToken [System.DateTime]$ValidAfter [System.DateTime]$ValidUntil [string[]]$Scopes [string]$RefreshToken [string]$Audience [string]$Issuer [PSObject]$TokenData #endregion Token Data #region Connection Data [string]$Service [string]$Type [string]$ClientID [string]$TenantID [string]$ServiceUrl [Hashtable]$Header = @{} # Workflow: Client Secret [System.Security.SecureString]$ClientSecret # Workflow: Certificate [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate # Workflow: Username & Password [PSCredential]$Credential #endregion Connection Data #region Constructors DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [Securestring]$ClientSecret, [string]$ServiceUrl) { $this.Service = $Service $this.ClientID = $ClientID $this.TenantID = $TenantID $this.ClientSecret = $ClientSecret $this.ServiceUrl = $ServiceUrl $this.Type = 'ClientSecret' } DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [string]$ServiceUrl) { $this.Service = $Service $this.ClientID = $ClientID $this.TenantID = $TenantID $this.Certificate = $Certificate $this.ServiceUrl = $ServiceUrl $this.Type = 'Certificate' } DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [pscredential]$Credential, [string]$ServiceUrl) { $this.Service = $Service $this.ClientID = $ClientID $this.TenantID = $TenantID $this.Credential = $Credential $this.ServiceUrl = $ServiceUrl $this.Type = 'UsernamePassword' } DefenderToken([string]$Service, [string]$ClientID, [string]$TenantID, [string]$ServiceUrl, [bool]$IsDeviceCode) { $this.Service = $Service $this.ClientID = $ClientID $this.TenantID = $TenantID $this.ServiceUrl = $ServiceUrl if ($IsDeviceCode) { $this.Type = 'DeviceCode' } else { $this.Type = 'Browser' } } #endregion Constructors [void]SetTokenMetadata([PSObject] $AuthToken) { $this.AccessToken = $AuthToken.AccessToken $this.ValidAfter = $AuthToken.ValidAfter $this.ValidUntil = $AuthToken.ValidUntil $this.Scopes = $AuthToken.Scopes if ($AuthToken.RefreshToken) { $this.RefreshToken = $AuthToken.RefreshToken } $tokenPayload = $AuthToken.AccessToken.Split(".")[1].Replace('-', '+').Replace('_', '/') while ($tokenPayload.Length % 4) { $tokenPayload += "=" } $bytes = [System.Convert]::FromBase64String($tokenPayload) $data = [System.Text.Encoding]::ASCII.GetString($bytes) | ConvertFrom-Json if ($data.roles) { $this.Scopes = $data.roles } elseif ($data.scp) { $this.Scopes = $data.scp -split " " } $this.Audience = $data.aud $this.Issuer = $data.iss $this.TokenData = $data } [hashtable]GetHeader() { if ($this.ValidUntil -lt (Get-Date).AddMinutes(5)) { $this.RenewToken() } $currentHeader = @{} if ($this.Header.Count -gt 0) { $currentHeader = $this.Header.Clone() } $currentHeader.Authorization = "Bearer $($this.AccessToken)" return $currentHeader } [void]RenewToken() { $defaultParam = @{ ServiceUrl = $this.ServiceUrl TenantID = $this.TenantID ClientID = $this.ClientID } switch ($this.Type) { Certificate { $result = Connect-ServiceCertificate @defaultParam -Certificate $this.Certificate $this.SetTokenMetadata($result) } ClientSecret { $result = Connect-ServiceClientSecret @defaultParam -ClientSecret $this.ClientSecret $this.SetTokenMetadata($result) } UsernamePassword { $result = Connect-ServicePassword @defaultParam -Credential $this.Credential $this.SetTokenMetadata($result) } DeviceCode { if ($this.RefreshToken) { Connect-ServiceRefreshToken -Token $this return } $result = Connect-ServiceDeviceCode @defaultParam $this.SetTokenMetadata($result) } Browser { if ($this.RefreshToken) { Connect-ServiceRefreshToken -Token $this return } $result = Connect-ServiceBrowser @defaultParam -SelectAccount $this.SetTokenMetadata($result) } } } } <# # Example: Register-PSFTeppScriptblock -Name "DefenderAPI.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } #> Register-PSFTeppScriptblock -Name 'DefenderAPI.Service' -ScriptBlock { (Get-DefenderAPIService).Name } -Global <# # Example: Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name DefenderAPI.alcohol #> Register-PSFTeppArgumentCompleter -Command Connect-DefenderAPIService -Parameter Service -Name DefenderAPI.Service function Connect-ServiceBrowser { <# .SYNOPSIS Interactive logon using the Authorization flow and browser. Supports SSO. .DESCRIPTION Interactive logon using the Authorization flow and browser. Supports SSO. This flow requires an App Registration configured for the platform "Mobile and desktop applications". Its redirect Uri must be "http://localhost" On successful authentication .PARAMETER ClientID The ID of the registered app used with this authentication request. .PARAMETER TenantID The ID of the tenant connected to with this authentication request. .PARAMETER SelectAccount Forces account selection on logon. As this flow supports single-sign-on, it will otherwise not prompt for anything if already signed in. This could be a problem if you want to connect using another (e.g. an admin) account. .PARAMETER Scopes Generally doesn't need to be changed from the default '.default' .PARAMETER LocalPort The local port that should be redirected to. In order to process the authentication response, we need to listen to a local web request on some port. Usually needs not be redirected. Defaults to: 8080 .PARAMETER Resource The resource owning the api permissions / scopes requested. .PARAMETER Browser The path to the browser to use for the authentication flow. Provide the full path to the executable. The browser must accept the url to open as its only parameter. Defaults to your default browser. .PARAMETER NoReconnect Disables automatic reconnection. By default, this module will automatically try to reaquire a new token before the old one expires. .EXAMPLE PS C:\> Connect-ServiceBrowser -ClientID '<ClientID>' -TenantID '<TenantID>' Connects to the specified tenant using the specified client, prompting the user to authorize via Browser. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $TenantID, [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $Resource, [switch] $SelectAccount, [AllowEmptyCollection()] [string[]] $Scopes, [int] $LocalPort = 8080, [string] $Browser, [switch] $NoReconnect ) process { Add-Type -AssemblyName System.Web if (-not $Scopes) { $Scopes = @('.default') } $redirectUri = "http://localhost:$LocalPort" $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource if (-not $NoReconnect) { $actualScopes = @($actualScopes) + 'offline_access' } $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/authorize?" $state = Get-Random $parameters = @{ client_id = $ClientID response_type = 'code' redirect_uri = $redirectUri response_mode = 'query' scope = $actualScopes -join ' ' state = $state } if ($SelectAccount) { $parameters.prompt = 'select_account' } $paramStrings = foreach ($pair in $parameters.GetEnumerator()) { $pair.Key, ([System.Web.HttpUtility]::UrlEncode($pair.Value)) -join '=' } $uriFinal = $uri + ($paramStrings -join '&') Write-PSFMessage -Level Verbose -String 'Connect-ServiceBrowser.AuthorizeUri' -StringValues $uriFinal $redirectTo = 'https://raw.githubusercontent.com/FriedrichWeinmann/MiniGraph/master/nothing-to-see-here.txt' if ((Get-Random -Minimum 10 -Maximum 99) -eq 66) { $redirectTo = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' } # Start local server to catch the redirect $http = [System.Net.HttpListener]::new() $http.Prefixes.Add("$redirectUri/") try { $http.Start() } catch { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Failed to create local http listener on port $LocalPort. Use -LocalPort to select a different port. $_" -Category OpenError } # Execute in default browser if ($Browser) { & $Browser $uriFinal } else { Start-Process $uriFinal } # Get Result $task = $http.GetContextAsync() $authorizationCode, $stateReturn, $sessionState = $null try { while (-not $task.IsCompleted) { Start-Sleep -Milliseconds 200 } $context = $task.Result $context.Response.Redirect($redirectTo) $context.Response.Close() $authorizationCode, $stateReturn, $sessionState = $context.Request.Url.Query -split "&" } finally { $http.Stop() $http.Dispose() } if (-not $stateReturn) { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Authentication failed (see browser for details)" -Category AuthenticationError } if ($stateReturn -match '^error_description=') { $message = $stateReturn -replace '^error_description=' -replace '\+',' ' $message = [System.Web.HttpUtility]::UrlDecode($message) Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Error processing the request: $message" -Category InvalidOperation } if ($state -ne $stateReturn.Split("=")[1]) { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Received invalid authentication result. Likely returned from another flow redirecting to the same local port!" -Category InvalidOperation } $actualAuthorizationCode = $authorizationCode.Split("=")[1] $body = @{ client_id = $ClientID scope = $actualScopes -join " " code = $actualAuthorizationCode redirect_uri = $redirectUri grant_type = 'authorization_code' } $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" try { $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body -ErrorAction Stop } catch { if ($_ -notmatch '"error":\s*"invalid_client"') { Invoke-TerminatingException -Cmdlet $PSCmdlet -ErrorRecord $_ } Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "The App Registration $ClientID has not been configured correctly. Ensure you have a 'Mobile and desktop applications' platform with redirect to 'http://localhost' configured (and not a 'Web' Platform). $_" -Category $_.CategoryInfo.Category } Read-AuthResponse -AuthResponse $authResponse } } function Connect-ServiceCertificate { <# .SYNOPSIS Connects to AAD using a application ID and a certificate. .DESCRIPTION Connects to AAD using a application ID and a certificate. .PARAMETER Resource The resource owning the api permissions / scopes requested. .PARAMETER Certificate The certificate to use for authentication. .PARAMETER TenantID The ID of the tenant/directory to connect to. .PARAMETER ClientID The ID of the registered application used to authenticate as. .EXAMPLE PS C:\> Connect-ServiceCertificate -Certificate $cert -TenantID $tenantID -ClientID $clientID Connects to the specified tenant using the specified app & cert. .LINK https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Resource, [Parameter(Mandatory = $true)] [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Parameter(Mandatory = $true)] [string] $TenantID, [Parameter(Mandatory = $true)] [string] $ClientID ) #region Build Signature Payload $jwtHeader = @{ alg = "RS256" typ = "JWT" x5t = [Convert]::ToBase64String($Certificate.GetCertHash()) -replace '\+', '-' -replace '/', '_' -replace '=' } $encodedHeader = $jwtHeader | ConvertTo-Json | ConvertTo-Base64 $claims = @{ aud = "https://login.microsoftonline.com/$TenantID/v2.0" exp = ((Get-Date).AddMinutes(5) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int] iss = $ClientID jti = "$(New-Guid)" nbf = ((Get-Date) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int] sub = $ClientID } $encodedClaims = $claims | ConvertTo-Json | ConvertTo-Base64 $jwtPreliminary = $encodedHeader, $encodedClaims -join "." $jwtSigned = ($jwtPreliminary | ConvertTo-SignedString -Certificate $Certificate) -replace '\+', '-' -replace '/', '_' -replace '=' $jwt = $jwtPreliminary, $jwtSigned -join '.' #endregion Build Signature Payload $body = @{ client_id = $ClientID client_assertion = $jwt client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' scope = '{0}/.default' -f $Resource grant_type = 'client_credentials' } $header = @{ Authorization = "Bearer $jwt" } $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" try { $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Headers $header -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop } catch { throw } Read-AuthResponse -AuthResponse $authResponse } function Connect-ServiceClientSecret { <# .SYNOPSIS Connets using a client secret. .DESCRIPTION Connets using a client secret. .PARAMETER Resource The resource owning the api permissions / scopes requested. .PARAMETER ClientID The ID of the registered app used with this authentication request. .PARAMETER TenantID The ID of the tenant connected to with this authentication request. .PARAMETER ClientSecret The actual secret used for authenticating the request. .EXAMPLE PS C:\> Connect-ServiceClientSecret -ClientID '<ClientID>' -TenantID '<TenantID>' -ClientSecret $secret Connects to the specified tenant using the specified client and secret. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Resource, [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [Parameter(Mandatory = $true)] [securestring] $ClientSecret ) process { $body = @{ resource = $Resource client_id = $ClientID client_secret = [PSCredential]::new('NoMatter', $ClientSecret).GetNetworkCredential().Password grant_type = 'client_credentials' } try { $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body $body -ErrorAction Stop } catch { throw } Read-AuthResponse -AuthResponse $authResponse } } function Connect-ServiceDeviceCode { <# .SYNOPSIS Connects to Azure AD using the Device Code authentication workflow. .DESCRIPTION Connects to Azure AD using the Device Code authentication workflow. .PARAMETER Resource The resource owning the api permissions / scopes requested. .PARAMETER ClientID The ID of the registered app used with this authentication request. .PARAMETER TenantID The ID of the tenant connected to with this authentication request. .PARAMETER Scopes The scopes to request. Automatically scoped to the service specified via Service Url. Defaults to ".Default" .EXAMPLE PS C:\> Connect-ServiceDeviceCode -ServiceUrl $url -ClientID '<ClientID>' -TenantID '<TenantID>' Connects to the specified tenant using the specified client, prompting the user to authorize via Browser. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Resource, [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [AllowEmptyCollection()] [string[]] $Scopes ) if (-not $Scopes) { $Scopes = @('.default') } $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource try { $initialResponse = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/devicecode" -Body @{ client_id = $ClientID scope = @($actualScopes) + 'offline_access' -join " " } -ErrorAction Stop } catch { throw } Write-Host $initialResponse.message $paramRetrieve = @{ Uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" Method = "POST" Body = @{ grant_type = "urn:ietf:params:oauth:grant-type:device_code" client_id = $ClientID device_code = $initialResponse.device_code } ErrorAction = 'Stop' } $limit = (Get-Date).AddSeconds($initialResponse.expires_in) while ($true) { if ((Get-Date) -gt $limit) { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Timelimit exceeded, device code authentication failed" -Category AuthenticationError } Start-Sleep -Seconds $initialResponse.interval try { $authResponse = Invoke-RestMethod @paramRetrieve } catch { if ($_ -match '"error":\s*"authorization_pending"') { continue } $PSCmdlet.ThrowTerminatingError($_) } if ($authResponse) { break } } Read-AuthResponse -AuthResponse $authResponse } function Connect-ServicePassword { <# .SYNOPSIS Connect to graph using username and password. .DESCRIPTION Connect to graph using username and password. This logs into graph as a user, not as an application. Only cloud-only accounts can be used for this workflow. Consent to scopes must be granted before using them, as this command cannot show the consent prompt. .PARAMETER Resource The resource owning the api permissions / scopes requested. .PARAMETER Credential Credentials of the user to connect as. .PARAMETER TenantID The Guid of the tenant to connect to. .PARAMETER ClientID The ClientID / ApplicationID of the application to use. .PARAMETER Scopes The permission scopes to request. .EXAMPLE PS C:\> Connect-ServicePassword -Credential max@contoso.com -ClientID $client -TenantID $tenant -Scopes 'user.read','user.readbasic.all' Connect as max@contoso.com with the rights to read user information. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Resource, [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [string[]] $Scopes = '.default' ) $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource $request = @{ client_id = $ClientID scope = $actualScopes -join " " username = $Credential.UserName password = $Credential.GetNetworkCredential().Password grant_type = 'password' } try { $authResponse = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Body $request -ErrorAction Stop } catch { throw } Read-AuthResponse -AuthResponse $authResponse } function Connect-ServiceRefreshToken { <# .SYNOPSIS Connect with the refresh token provided previously. .DESCRIPTION Connect with the refresh token provided previously. Used mostly for delegate authentication flows to avoid interactivity. .PARAMETER Token The DefenderToken object with the refresh token to use. The token is then refreshed in-place with no output provided. .EXAMPLE PS C:\> Connect-ServiceRefreshToken Connect with the refresh token provided previously. #> [CmdletBinding()] param ( $Token ) process { if (-not $Token.RefreshToken) { throw "Failed to refresh token: No refresh token found!" } $scopes = $Token.Scopes $body = @{ client_id = $Token.ClientID scope = $scopes -join " " refresh_token = $Token.RefreshToken grant_type = 'refresh_token' } $uri = "https://login.microsoftonline.com/$($Token.TenantID)/oauth2/v2.0/token" $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body $Token.SetTokenMetadata((Read-AuthResponse -AuthResponse $authResponse)) } } function Read-AuthResponse { <# .SYNOPSIS Produces a standard output representation of the authentication response received. .DESCRIPTION Produces a standard output representation of the authentication response received. This streamlines the token processing and simplifies the connection code. .PARAMETER AuthResponse The authentication response received. .EXAMPLE PS C:\> Read-AuthResponse -AuthResponse $authResponse Reads the authentication details received. #> [CmdletBinding()] param ( $AuthResponse ) process { if ($AuthResponse.expires_in) { $after = (Get-Date).AddMinutes(-5) $until = (Get-Date).AddSeconds($AuthResponse.expires_in) } else { $after = (Get-Date -Date '1970-01-01').AddSeconds($AuthResponse.not_before).ToLocalTime() $until = (Get-Date -Date '1970-01-01').AddSeconds($AuthResponse.expires_on).ToLocalTime() } $scopes = @() if ($AuthResponse.scope) { $scopes = $authResponse.scope -split " " } [pscustomobject]@{ AccessToken = $AuthResponse.access_token ValidAfter = $after ValidUntil = $until Scopes = $scopes RefreshToken = $AuthResponse.refresh_token } } } function ConvertTo-Base64 { <# .SYNOPSIS Converts the input-string to its base 64 encoded string form. .DESCRIPTION Converts the input-string to its base 64 encoded string form. .PARAMETER Text The text to convert. .PARAMETER Encoding The encoding of the input text. Used to correctly translate the input string into bytes before converting those to base 64. Defaults to UTF8 .EXAMPLE PS C:\> Get-Content .\code.ps1 -Raw | ConvertTo-Base64 Reads the input file and converts its content into base64. #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]] $Text, [System.Text.Encoding] $Encoding = [System.Text.Encoding]::UTF8 ) process { foreach ($entry in $Text) { $bytes = $Encoding.GetBytes($entry) [Convert]::ToBase64String($bytes) } } } function ConvertTo-Hashtable { <# .SYNOPSIS Converts input objects into hashtables. .DESCRIPTION Converts input objects into hashtables. Allows explicitly including some properties only and remapping key-names as required. .PARAMETER Include Only select the specified properties. .PARAMETER Mapping Remap hashtable/property keys. This allows you to rename parameters before passing them through to other commands. Example: @{ Select = '$select' } This will map the "Select"-property/key on the input object to be '$select' on the output item. .PARAMETER InputObject The object to convert. .EXAMPLE PS C:\> $__body = $PSBoundParameters | ConvertTo-Hashtable -Include Name, UserID -Mapping $__mapping Converts the object $PSBoundParameters into a hashtable, including the keys "Name" and "UserID" and remapping them as specified in $__mapping #> [OutputType([hashtable])] [CmdletBinding()] param ( [AllowEmptyCollection()] [string[]] $Include, [Hashtable] $Mapping = @{ }, [Parameter(ValueFromPipeline = $true)] $InputObject ) process { $result = @{ } if ($InputObject -is [System.Collections.IDictionary]) { foreach ($pair in $InputObject.GetEnumerator()) { if ($pair.Key -notin $Include) { continue } if ($Mapping[$pair.Key]) { $result[$Mapping[$pair.Key]] = $pair.Value } else { $result[$pair.Key] = $pair.Value } } } else { foreach ($property in $InputObject.PSObject.Properties) { if ($property.Name -notin $Include) { continue } if ($Mapping[$property.Name]) { $result[$Mapping[$property.Name]] = $property.Value } else { $result[$property.Name] = $property.Value } } } $result } } function ConvertTo-QueryString { <# .SYNOPSIS Convert conditions in a hashtable to a Query string to append to a webrequest. .DESCRIPTION Convert conditions in a hashtable to a Query string to append to a webrequest. .PARAMETER QueryHash Hashtable of query modifiers - usually filter conditions - to include in a web request. .EXAMPLE PS C:\> ConvertTo-QueryString -QueryHash $Query Converts the conditions in the specified hashtable to a Query string to append to a webrequest. #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Hashtable] $QueryHash ) process { $elements = foreach ($pair in $QueryHash.GetEnumerator()) { '{0}={1}' -f $pair.Name, ($pair.Value -join ",") } '?{0}' -f ($elements -join '&') } } function ConvertTo-SignedString { <# .SYNOPSIS Signs input string with the offered certificate. .DESCRIPTION Signs input string with the offered certificate. .PARAMETER Text The text to sign. .PARAMETER Certificate The certificate to sign with. The Private Key must be available. .PARAMETER Padding What RSA Signature padding to use. Defaults to Pkcs1 .PARAMETER Algorithm What algorithm to use for signing. Defaults to SHA256 .PARAMETER Encoding The encoding to use for transforming the text to bytes before signing it. Defaults to UTF8 .EXAMPLE PS C:\> ConvertTo-SignedString -Text $token Signs the specified token #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]] $Text, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Security.Cryptography.RSASignaturePadding] $Padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1, [Security.Cryptography.HashAlgorithmName] $Algorithm = [Security.Cryptography.HashAlgorithmName]::SHA256, [System.Text.Encoding] $Encoding = [System.Text.Encoding]::UTF8 ) begin { $privateKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate) } process { foreach ($entry in $Text) { $inBytes = $Encoding.GetBytes($entry) $outBytes = $privateKey.SignData($inBytes, $Algorithm, $Padding) [convert]::ToBase64String($outBytes) } } } function Invoke-TerminatingException { <# .SYNOPSIS Throw a terminating exception in the context of the caller. .DESCRIPTION Throw a terminating exception in the context of the caller. Masks the actual code location from the end user in how the message will be displayed. .PARAMETER Cmdlet The $PSCmdlet variable of the calling command. .PARAMETER Message The message to show the user. .PARAMETER Exception A nested exception to include in the exception object. .PARAMETER Category The category of the error. .PARAMETER ErrorRecord A full error record that was caught by the caller. Use this when you want to rethrow an existing error. .EXAMPLE PS C:\> Invoke-TerminatingException -Cmdlet $PSCmdlet -Message 'Unknown calling module' Terminates the calling command, citing an unknown caller. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] $Cmdlet, [string] $Message, [System.Exception] $Exception, [System.Management.Automation.ErrorCategory] $Category = [System.Management.Automation.ErrorCategory]::NotSpecified, [System.Management.Automation.ErrorRecord] $ErrorRecord ) process{ if ($ErrorRecord -and -not $Message) { $Cmdlet.ThrowTerminatingError($ErrorRecord) } $exceptionType = switch ($Category) { default { [System.Exception] } 'InvalidArgument' { [System.ArgumentException] } 'InvalidData' { [System.IO.InvalidDataException] } 'AuthenticationError' { [System.Security.Authentication.AuthenticationException] } 'InvalidOperation' { [System.InvalidOperationException] } } if ($Exception) { $newException = $Exception.GetType()::new($Message, $Exception) } elseif ($ErrorRecord) { $newException = $ErrorRecord.Exception.GetType()::new($Message, $ErrorRecord.Exception) } else { $newException = $exceptionType::new($Message) } $record = [System.Management.Automation.ErrorRecord]::new($newException, (Get-PSCallStack)[1].FunctionName, $Category, $Target) $Cmdlet.ThrowTerminatingError($record) } } function Resolve-Certificate { <# .SYNOPSIS Helper function to resolve certificate input. .DESCRIPTION Helper function to resolve certificate input. This function expects the full $PSBoundParameters from the calling command and will (in this order) look for these parameter names: + Certificate: A full X509Certificate2 object with private key + CertificateThumbprint: The thumbprint of a certificate to use. Will look first in the user store, then the machine store for it. + CertificateName: The subject of the certificate to look for. Will look first in the user store, then the machine store for it. Will select the certificate with the longest expiration period. + CertificatePath: Path to a PFX file to load. Also expects a CertificatePassword parameter to unlock the file. .PARAMETER BoundParameters The $PSBoundParameter variable of the caller to simplify passthrough. See Description for more details on what the command expects, .EXAMPLE PS C:\> $certificateObject = Resolve-Certificate -BoundParameters $PSBoundParameters Resolves the certificate based on the parameters provided to the calling command. #> [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] [CmdletBinding()] param ( $BoundParameters ) if ($BoundParameters.Certificate) { return $BoundParameters.Certificate } if ($BoundParameters.CertificateThumbprint) { if (Test-Path -Path "cert:\CurrentUser\My\$($BoundParameters.CertificateThumbprint)") { return Get-Item "cert:\CurrentUser\My\$($BoundParameters.CertificateThumbprint)" } if (Test-Path -Path "cert:\LocalMachine\My\$($BoundParameters.CertificateThumbprint)") { return Get-Item "cert:\LocalMachine\My\$($BoundParameters.CertificateThumbprint)" } Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to find certificate with thumbprint '$($BoundParameters.CertificateThumbprint)'" } if ($BoundParameters.CertificateName) { if ($certificate = (Get-ChildItem 'Cert:\CurrentUser\My\').Where{ $_.Subject -eq $BoundParameters.CertificateName -and $_.HasPrivateKey }) { return $certificate | Sort-Object NotAfter -Descending | Select-Object -First 1 } if ($certificate = (Get-ChildItem 'Cert:\LocalMachine\My\').Where{ $_.Subject -eq $BoundParameters.CertificateName -and $_.HasPrivateKey }) { return $certificate | Sort-Object NotAfter -Descending | Select-Object -First 1 } Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to find certificate with subject '$($BoundParameters.CertificateName)'" } if ($BoundParameters.CertificatePath) { try { [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($BoundParameters.CertificatePath, $BoundParameters.CertificatePassword) } catch { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Unable to load certificate from file '$($BoundParameters.CertificatePath)': $_" -ErrorRecord $_ } } } function Resolve-ScopeName { <# .SYNOPSIS Normalizes scope names. .DESCRIPTION Normalizes scope names. To help manage correct scopes naming with services that don't map directly to their urls. .PARAMETER Scopes The scopes to normalize. .PARAMETER Resource The Resource the scopes are meant for. .EXAMPLE PS C:\> $scopes | Resolve-ScopeName -Resource $Resource Resolves all them scopes #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [string[]] $Scopes, [Parameter(Mandatory = $true)] [string] $Resource ) process { foreach ($scope in $Scopes) { foreach ($scope in $Scopes) { if ($scope -like 'https://*/*') { $scope } elseif ($scope -like 'api:/') { $scope } else { "{0}/{1}" -f $Resource, $scope } } } } } function Assert-DefenderAPIConnection { <# .SYNOPSIS Asserts a connection has been established. .DESCRIPTION Asserts a connection has been established. Fails the calling command in a terminating exception if not connected yet. .PARAMETER Service The service to which a connection needs to be established. .PARAMETER Cmdlet The $PSCmdlet variable of the calling command. Used to execute the terminating exception in the caller scope if needed. .PARAMETER RequiredScopes Scopes needed, for better error messages. .EXAMPLE PS C:\> Assert-DefenderAPIConnection -Service 'Endpoint' -Cmdlet $PSCmdlet Silently does nothing if already connected to the specified defender service. Kills the calling command if not yet connected. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Service, [Parameter(Mandatory = $true)] $Cmdlet, [AllowEmptyCollection()] [string[]] $RequiredScopes ) process { if ($script:_DefenderTokens["$Service"]) { return } $message = "Not connected yet! Use Connect-DefenderAPIService to establish a connection to '$Service' first." if ($RequiredScopes) { $message = $message + " Scopes required for this call: $($RequiredScopes -join ', ')"} Invoke-TerminatingException -Cmdlet $Cmdlet -Message -Category ConnectionError } } function Connect-DefenderAPI { <# .SYNOPSIS Establish a connection to the defender APIs. .DESCRIPTION Establish a connection to the defender APIs. Prerequisite before executing any requests / commands. .PARAMETER ClientID ID of the registered/enterprise application used for authentication. .PARAMETER TenantID The ID of the tenant/directory to connect to. .PARAMETER Scopes Any scopes to include in the request. Only used for interactive/delegate workflows, ignored for Certificate based authentication or when using Client Secrets. .PARAMETER Browser Use an interactive logon in your default browser. This is the default logon experience. .PARAMETER DeviceCode Use the Device Code delegate authentication flow. This will prompt the user to complete login via browser. .PARAMETER Certificate The Certificate object used to authenticate with. Part of the Application Certificate authentication workflow. .PARAMETER CertificateThumbprint Thumbprint of the certificate to authenticate with. The certificate must be stored either in the user or computer certificate store. Part of the Application Certificate authentication workflow. .PARAMETER CertificateName The name/subject of the certificate to authenticate with. The certificate must be stored either in the user or computer certificate store. The newest certificate with a private key will be chosen. Part of the Application Certificate authentication workflow. .PARAMETER CertificatePath Path to a PFX file containing the certificate to authenticate with. Part of the Application Certificate authentication workflow. .PARAMETER CertificatePassword Password to use to read a PFX certificate file. Only used together with -CertificatePath. Part of the Application Certificate authentication workflow. .PARAMETER ClientSecret The client secret configured in the registered/enterprise application. Part of the Client Secret Certificate authentication workflow. .PARAMETER Credential The username / password to authenticate with. Part of the Resource Owner Password Credential (ROPC) workflow. .PARAMETER Service The service to connect to. Individual commands using Invoke-MdeRequest specify the service to use and thus identify the token needed. Defaults to: Endpoint .PARAMETER ServiceUrl The base url to the service connecting to. Used for authentication, scopes and executing requests. Defaults to: https://api.securitycenter.microsoft.com/api .EXAMPLE PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID Establish a connection to the defender for endpoint API, prompting the user for login on their default browser. .EXAMPLE PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -Certificate $cert Establish a connection to the defender APIs using the provided certificate. .EXAMPLE PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -CertificatePath C:\secrets\certs\mde.pfx -CertificatePassword (Read-Host -AsSecureString) Establish a connection to the defender APIs using the provided certificate file. Prompts you to enter the certificate-file's password first. .EXAMPLE PS C:\> Connect-DefenderAPI -ClientID $clientID -TenantID $tenantID -ClientSecret $secret Establish a connection to the defender APIs using a client secret. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [CmdletBinding(DefaultParameterSetName = 'Browser')] param ( [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [string[]] $Scopes, [Parameter(ParameterSetName = 'Browser')] [switch] $Browser, [Parameter(ParameterSetName = 'DeviceCode')] [switch] $DeviceCode, [Parameter(ParameterSetName = 'AppCertificate')] [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Parameter(ParameterSetName = 'AppCertificate')] [string] $CertificateThumbprint, [Parameter(ParameterSetName = 'AppCertificate')] [string] $CertificateName, [Parameter(ParameterSetName = 'AppCertificate')] [string] $CertificatePath, [Parameter(ParameterSetName = 'AppCertificate')] [System.Security.SecureString] $CertificatePassword, [Parameter(Mandatory = $true, ParameterSetName = 'AppSecret')] [System.Security.SecureString] $ClientSecret, [Parameter(Mandatory = $true, ParameterSetName = 'UsernamePassword')] [PSCredential] $Credential, [PsfArgumentCompleter('DefenderAPI.Service')] [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')] [string[]] $Service = 'Endpoint', [string] $ServiceUrl ) process { foreach ($serviceName in $Service) { $serviceObject = Get-DefenderAPIService -Name $serviceName $commonParam = @{ ClientID = $ClientID TenantID = $TenantID Resource = $serviceObject.Resource } $effectiveServiceUrl = $ServiceUrl if (-not $ServiceUrl) { $effectiveServiceUrl = $serviceObject.ServiceUrl } #region Connection switch ($PSCmdlet.ParameterSetName) { #region Browser Browser { $scopesToUse = $Scopes if (-not $Scopes) { $scopesToUse = $serviceObject.DefaultScopes } Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.Browser' -ActionStringValues $serviceName -ScriptBlock { $result = Connect-ServiceBrowser @commonParam -SelectAccount -Scopes $scopesToUse -ErrorAction Stop } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $effectiveServiceUrl, $false) if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() } $token.SetTokenMetadata($result) $script:_DefenderTokens[$serviceName] = $token } #endregion Browser #region DeviceCode DeviceCode { $scopesToUse = $Scopes if (-not $Scopes) { $scopesToUse = $serviceObject.DefaultScopes } Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.DeviceCode' -ActionStringValues $serviceName -ScriptBlock { $result = Connect-ServiceDeviceCode @commonParam -Scopes $scopesToUse -ErrorAction Stop } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $effectiveServiceUrl, $true) if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() } $token.SetTokenMetadata($result) $script:_DefenderTokens[$serviceName] = $token } #endregion DeviceCode #region ROPC UsernamePassword { Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.ROPC' -ActionStringValues $serviceName -ScriptBlock { $result = Connect-ServicePassword @commonParam -Credential $Credential -ErrorAction Stop } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $Credential, $effectiveServiceUrl) if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() } $token.SetTokenMetadata($result) $script:_DefenderTokens[$serviceName] = $token } #endregion ROPC #region AppSecret AppSecret { Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.ClientSecret' -ActionStringValues $serviceName -ScriptBlock { $result = Connect-ServiceClientSecret @commonParam -ClientSecret $ClientSecret -ErrorAction Stop } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $ClientSecret, $effectiveServiceUrl) if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() } $token.SetTokenMetadata($result) $script:_DefenderTokens[$serviceName] = $token } #endregion AppSecret #region AppCertificate AppCertificate { try { $certificateObject = Resolve-Certificate -BoundParameters $PSBoundParameters } catch { Stop-PSFFunction -String 'Connect-DefenderAPI.Error.CertError' -StringValues $serviceName -Tag connect, fail -ErrorRecord $_ -EnableException $true -Cmdlet $PSCmdlet -Target $serviceName } Invoke-PSFProtectedCommand -ActionString 'Connect-DefenderAPI.Connect.Certificate' -ActionStringValues $serviceName, $certificateObject.Subject, $certificateObject.Thumbprint -ScriptBlock { $result = Connect-ServiceCertificate @commonParam -Certificate $certificateObject -ErrorAction Stop } -Target $serviceName -EnableException $true -PSCmdlet $PSCmdlet $token = [DefenderToken]::new($serviceName, $ClientID, $TenantID, $certificateObject, $effectiveServiceUrl) if ($serviceObject.Header.Count -gt 0) { $token.Header = $serviceObject.Header.Clone() } $token.SetTokenMetadata($result) $script:_DefenderTokens[$serviceName] = $token } #endregion AppCertificate } #endregion Connection } } } function Get-DefenderAPIService { <# .SYNOPSIS Returns the list of available defender API services that can be connected to. .DESCRIPTION Returns the list of available defender API services that can be connected to. Includes for each the endpoint/service url and the default requested scopes. .PARAMETER Name Name of the service to return. Defaults to: * .EXAMPLE PS C:\> Get-DefenderAPIService List all available services. #> [CmdletBinding()] param ( [PsfArgumentCompleter('DefenderAPI.Service')] [string] $Name = '*' ) process { $script:_DefenderEndpoints.Values | Where-Object Name -like $Name } } function Get-DefenderAPIToken { <# .SYNOPSIS Returns the session token of a defender API connection. .DESCRIPTION Returns the session token of a defender API connection. The main use for those token objects is calling their "GetHeader()" method to get an authentication header that automatically refreshes tokens as needed. .PARAMETER Service The service for which to retrieve the token. Defaults to: * .EXAMPLE PS C:\> Get-DefenderAPIToken Returns all current session tokens #> [CmdletBinding()] param ( [PsfArgumentCompleter('DefenderAPI.Service')] [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')] [string] $Service = '*' ) process { $script:_DefenderTokens.Values | Where-Object Service -like $Service } } function Register-DefenderAPIService { <# .SYNOPSIS Define a new Defender API Service to connect to. .DESCRIPTION Define a new Defender API Service to connect to. This allows defining new endpoints to connect to ... or overriding existing endpoints to a different configuration. .PARAMETER Name Name of the Service. .PARAMETER ServiceUrl The base Url requests will use. .PARAMETER Resource The Resource ID. Used when connecting to identify which scopes of an App Registration to use. .PARAMETER DefaultScopes Default scopes to request. Used in interactive delegate flows to provide a good default user experience. Default scopes should usually include common read scenarios. .PARAMETER Header Header data to include in each request. .PARAMETER HelpUrl Link for more information about this service. Ideally to documentation that helps setting up the connection. .EXAMPLE PS C:\> Register-DefenderAPIService -Name Endpoint -ServiceUrl 'https://api.securitycenter.microsoft.com/api' -Resource 'https://api.securitycenter.microsoft.com' Registers the defender for endpoint API as a service. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] [string] $ServiceUrl, [Parameter(Mandatory = $true)] [string] $Resource, [AllowEmptyCollection()] [string[]] $DefaultScopes = @(), [hashtable] $Header = @{}, [string] $HelpUrl ) process { $script:_DefenderEndpoints[$Name] = [PSCustomObject]@{ PSTypeName = 'DefenderAPI.Service' Name = $Name ServiceUrl = $ServiceUrl Resource = $Resource DefaultScopes = $DefaultScopes Header = $Header HelpUrl = $HelpUrl } } } function Set-DefenderAPIService { <# .SYNOPSIS Modify the settings on an existing Service configuration. .DESCRIPTION Modify the settings on an existing Service configuration. Service configurations are defined using Register-DefenderAPIService and define how connections and requests to a specific API service / endpoint are performed. .PARAMETER Name The name of the already existing Service configuration. .PARAMETER ServiceUrl The base Url requests will use. .PARAMETER Resource The Resource ID. Used when connecting to identify which scopes of an App Registration to use. .PARAMETER DefaultScopes Default scopes to request. Used in interactive delegate flows to provide a good default user experience. Default scopes should usually include common read scenarios. .PARAMETER Header Header data to include in each request. .PARAMETER HelpUrl Link for more information about this service. Ideally to documentation that helps setting up the connection. .EXAMPLE PS C:\> Set-DefenderAPIService -Name Endpoint -ServiceUrl 'https://api-us.securitycenter.microsoft.com/api' Changes the service url for the "Endpoint" service to 'https://api-us.securitycenter.microsoft.com/api'. Note: It is generally recommened to select the service url most suitable for your tenant, geographically: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/exposed-apis-list?view=o365-worldwide#versioning #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [PsfArgumentCompleter('DefenderAPI.Service')] [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')] [string] $Name, [string] $ServiceUrl, [string] $Resource, [AllowEmptyCollection()] [string[]] $DefaultScopes, [Hashtable] $Header, [string] $HelpUrl ) process { $service = $script:_DefenderEndpoints.$Name if ($PSBoundParameters.Keys -contains 'ServiceUrl') { $service.ServiceUrl = $ServiceUrl } if ($PSBoundParameters.Keys -contains 'Resource') { $service.Resource = $Resource } if ($PSBoundParameters.Keys -contains 'DefaultScopes') { $service.DefaultScopes = $DefaultScopes } if ($PSBoundParameters.Keys -contains 'Header') { $service.Header = $Header } if ($PSBoundParameters.Keys -contains 'HelpUrl') { $service.HelpUrl = $HelpUrl } } } function Invoke-DefenderAPIRequest { <# .SYNOPSIS Executes a web request against a defender endpoint. .DESCRIPTION Executes a web request against a defender endpoint. Handles all the authentication details once connected using Connect-DefenderAPIService. .PARAMETER Path The relative path of the endpoint to query. For example, to retrieve defender for endpoint alerts, it would be a plain "alerts". To access details on a particular machine instead it would look thus: "machines/1e5bc9d7e413ddd7902c2932e418702b84d0cc07" .PARAMETER Body Any body content needed for the request. .PARAMETER Query Any query content to include in the request. In opposite to -Body this is attached to the request Url and usually used for filtering. .PARAMETER Method The Rest Method to use. Defaults to GET .PARAMETER RequiredScopes Any authentication scopes needed. Used for documentary purposes only. .PARAMETER Header Any additional headers to include on top of authentication and content-type. .PARAMETER Service Which service to execute against. Determines the API endpoint called to. Defaults to "Endpoint" .PARAMETER SerializationDepth How deeply to serialize the request body when converting it to json. Defaults to the value in the 'DefenderAPI.Request.SerializationDepth' configuration setting. This in turn defaults to "99" .EXAMPLE PS C:\> Invoke-DefenderAPIRequest -Path 'alerts' -RequiredScopes 'Alert.Read' Return a list of defender alerts. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [Hashtable] $Body = @{ }, [Hashtable] $Query = @{ }, [string] $Method = 'GET', [string[]] $RequiredScopes, [hashtable] $Header = @{}, [PsfArgumentCompleter('DefenderAPI.Service')] [PsfValidateSet(TabCompletion = 'DefenderAPI.Service')] [string] $Service = 'Endpoint', [ValidateRange(1,666)] [int] $SerializationDepth = (Get-PSFConfigValue -FullName 'DefenderAPI.Request.SerializationDepth' -Fallback 99) ) begin{ Assert-DefenderAPIConnection -Service $Service -Cmdlet $PSCmdlet -RequiredScopes $RequiredScopes $token = $script:_DefenderTokens.$Service } process { $parameters = @{ Method = $Method Uri = "$($token.ServiceUrl.Trim("/"))/$($Path.TrimStart('/'))" } if ($Body.Count -gt 0) { $parameters.Body = $Body | ConvertTo-Json -Compress -Depth $SerializationDepth } if ($Query.Count -gt 0) { $parameters.Uri += ConvertTo-QueryString -QueryHash $Query } while ($parameters.Uri) { $parameters.Headers = $token.GetHeader() + $Header # GetHeader() automatically refreshes expried tokens Write-PSFMessage -Level Debug -String 'Invoke-DefenderAPIRequest.Request' -StringValues $Method, $parameters.Uri try { $result = Invoke-RestMethod @parameters -ErrorAction Stop } catch { $letItBurn = $true $failure = $_ if ($_.ErrorDetails.Message) { $details = $_.ErrorDetails.Message | ConvertFrom-Json if ($details.Error.Code -eq 'TooManyRequests') { Write-PSFMessage -Level Verbose -Message $details.error.message $delay = 1 + ($details.error.message -replace '^.+ (\d+) .+$','$1' -as [int]) if ($delay -gt 5) { Write-PSFMessage -Level Warning -String 'Invoke-DefenderAPIRequest.Query.Throttling' -StringValues $delay } Start-Sleep -Seconds $delay try { $result = Invoke-RestMethod @parameters -ErrorAction Stop $letItBurn = $false } catch { $failure = $_ } } } if ($letItBurn) { Stop-PSFFunction -String 'Invoke-DefenderAPIRequest.Error.QueryFailed' -StringValues $Method, $Path -ErrorRecord $failure -EnableException $true -Cmdlet $PSCmdlet } } if ($result.PSObject.Properties.Where{ $_.Name -eq 'value' }) { $result.Value } else { $result } $parameters.Uri = $result.'@odata.nextLink' } } } function Invoke-MdAdvancedQuery { <# .SYNOPSIS Advanced Hunting .DESCRIPTION Run a custom query in Windows Defender ATP Scopes required (delegate auth): AdvancedQuery.Read .PARAMETER Query The query to run .EXAMPLE PS C:\> Invoke-MdAdvancedQuery -Query $query Run a custom query in Windows Defender ATP .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-advanced-query-api?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Query ) process { $__mapping = @{ 'Query' = 'Query' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'advancedqueries/run' Method = 'post' RequiredScopes = 'AdvancedQuery.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param | ConvertFrom-AdvancedQuery } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdAdvancedQuerySchema { <# .SYNOPSIS Advanced Hunting Schema .DESCRIPTION Gets the schema for a Windows Defender ATP custom query .PARAMETER Query The query to run .EXAMPLE PS C:\> Set-MdAdvancedQuerySchema -Query $query Gets the schema for a Windows Defender ATP custom query .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Query ) process { $__mapping = @{ 'Query' = 'Query' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'advancedqueries/schema' Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdAlert { <# .SYNOPSIS Alerts - Get list of alerts .DESCRIPTION Retrieve from Windows Defender ATP the most recent alerts Scopes required (delegate auth): Alert.Read .PARAMETER Top Returns only the first n results. .PARAMETER AlertID The identifier of the alert to retrieve .PARAMETER Orderby Sorts the results. .PARAMETER Select Selects which properties to include in the response, defaults to all. .PARAMETER Filter Filters the results, using OData syntax. .PARAMETER Expand Expands related entities inline. .PARAMETER Skip Skips the first n results. .PARAMETER Count Includes a count of the matching results in the response. .EXAMPLE PS C:\> Get-MdAlert Retrieve from Windows Defender ATP the most recent alerts .EXAMPLE PS C:\> Get-MdAlert -AlertID $alertid Retrieve from Windows Defender ATP a specific alert .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-alerts?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Top, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleAlert')] [string] $AlertID, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Orderby, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string[]] $Select, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Filter, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Expand, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Skip, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [boolean] $Count ) process { $__mapping = @{ 'Top' = '$top' 'Orderby' = '$orderby' 'Select' = '$select' 'Filter' = '$filter' 'Expand' = '$expand' 'Skip' = '$skip' 'Count' = '$count' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Filter','Expand','Skip','Count') -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'alerts' Method = 'get' RequiredScopes = 'Alert.Read' } if ($AlertID) { $__param.Path += "/$AlertID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function New-MdAlert { <# .SYNOPSIS Alerts - Create alert .DESCRIPTION Create Alert based on specific Event Scopes required (delegate auth): Alert.ReadWrite .PARAMETER EventTime Time of the event as string, e.g. 2018-08-03T16:45:21.7115183Z .PARAMETER MachineID ID of the machine on which the event was identified .PARAMETER Title Title of the Alert .PARAMETER Severity Severity of the alert. .PARAMETER Description Description of the Alert .PARAMETER RecommendedAction Recommended action for the Alert .PARAMETER Category Category of the alert .PARAMETER ReportID Report Id of the event .EXAMPLE PS C:\> New-MdAlert -Title $title -Severity $severity -Description $description -Category $category Create Alert based on specific Event .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/create-alert-by-reference?view=o365-worldwide #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $EventTime, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $MachineID, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Title, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Severity, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Description, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $RecommendedAction, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Category, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $ReportID ) process { $__mapping = @{ 'EventTime' = 'Event Time' 'MachineID' = 'Machine ID' 'Title' = 'Title' 'Severity' = 'Severity' 'Description' = 'Description' 'RecommendedAction' = 'Recommended Action' 'Category' = 'Category' 'ReportID' = 'Report ID' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('EventTime','MachineID','Title','Severity','Description','RecommendedAction','Category','ReportID') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'alerts/createAlertByReference' Method = 'post' RequiredScopes = 'Alert.ReadWrite' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdAlert { <# .SYNOPSIS Alerts - Update alert .DESCRIPTION Update a Windows Defender ATP alert Scopes required (delegate auth): Alert.ReadWrite .PARAMETER AlertID The identifier of the alert to update .PARAMETER Comment A comment to associate to the alert .PARAMETER Classification Classification of the alert. One of 'Unknown', 'FalsePositive', 'TruePositive' .PARAMETER Status Status of the alert. One of 'New', 'InProgress' and 'Resolved' .PARAMETER Determination The determination of the alert. One of 'NotAvailable', 'Apt', 'Malware', 'SecurityPersonnel', 'SecurityTesting', 'UnwantedSoftware', 'Other' .PARAMETER AssignedTo Person to assign the alert to .PARAMETER Confirm If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. .PARAMETER WhatIf If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. .EXAMPLE PS C:\> Set-MdAlert -AlertID $alertid Update a Windows Defender ATP alert .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/update-alert?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $AlertID, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Classification, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Status, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Determination, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $AssignedTo ) process { $__mapping = @{ 'Comment' = 'Comment' 'Classification' = 'Classification' 'Status' = 'Status' 'Determination' = 'Determination' 'AssignedTo' = 'Assigned to' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment','Classification','Status','Determination','AssignedTo') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'alerts/{AlertID}' -Replace '{AlertID}',$AlertID Method = 'patch' RequiredScopes = 'Alert.ReadWrite' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' if (-not $PSCmdlet.ShouldProcess("$AlertID","Update existing Alert")) { return } try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdDeviceSecureScore { <# .SYNOPSIS Retrieves your Microsoft Secure Score for Devices .DESCRIPTION Retrieves your Microsoft Secure Score for Devices. A higher Microsoft Secure Score for Devices means your endpoints are more resilient from cybersecurity threat attacks. Scopes required (delegate auth): Score.Read .EXAMPLE PS C:\> Get-MdDeviceSecureScore <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-device-secure-score?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'configurationScore' Method = 'get' RequiredScopes = 'Score.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdExposureScore { <# .SYNOPSIS Retrieves the organizational exposure score. .DESCRIPTION Retrieves the organizational exposure score. Scopes required (delegate auth): Score.Read .EXAMPLE PS C:\> Get-MdExposureScore <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-exposure-score?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'exposureScore' Method = 'get' RequiredScopes = 'Score.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineGroupExposureScore { <# .SYNOPSIS Retrieves a collection of alerts related to a given domain address. .DESCRIPTION Retrieves a collection of alerts related to a given domain address. Scopes required (delegate auth): Score.Read .EXAMPLE PS C:\> Get-MdMachineGroupExposureScore <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machine-group-exposure-score?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'exposureScore/byMachineGroups' Method = 'get' RequiredScopes = 'Score.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdFile { <# .SYNOPSIS Files - Get a single file .DESCRIPTION Retrieve from Windows Defender ATP a specific file by identifier Sha1, or Sha256 .PARAMETER FileID The file identifier - Sha1, or Sha256 .EXAMPLE PS C:\> Get-MdFile -FileID $fileid Retrieve from Windows Defender ATP a specific file by identifier Sha1, or Sha256 .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $FileID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'files/{FileID}' -Replace '{FileID}',$FileID Method = 'get' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdFileAlert { <# .SYNOPSIS Files - Get alerts related to a file .DESCRIPTION Retrieve from Windows Defender ATP a collection of alerts related to a given file by identifier Sha1, or Sha256 .PARAMETER FileID The file identifier - Sha1, or Sha256 .EXAMPLE PS C:\> Get-MdFileAlert -FileID $fileid Retrieve from Windows Defender ATP a collection of alerts related to a given file by identifier Sha1, or Sha256 .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $FileID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'files/{FileID}/alerts' -Replace '{FileID}',$FileID Method = 'get' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdFileMachine { <# .SYNOPSIS Files - Get machines related to a file .DESCRIPTION Retrieve from Windows Defender ATP a collection of machines related to a given file by identifier Sha1, or Sha256 .PARAMETER FileID The file identifier - Sha1, or Sha256 .EXAMPLE PS C:\> Get-MdFileMachine -FileID $fileid Retrieve from Windows Defender ATP a collection of machines related to a given file by identifier Sha1, or Sha256 .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $FileID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'files/{FileID}/machines' -Replace '{FileID}',$FileID Method = 'get' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdIndicator { <# .SYNOPSIS Indicators - Get list of all active indicators .DESCRIPTION Retrieve from Windows Defender ATP list of all active indicators .PARAMETER Top Returns only the first n results. .PARAMETER Orderby Sorts the results. .PARAMETER Select Selects which properties to include in the response, defaults to all. .PARAMETER Skip Skips the first n results. .PARAMETER Filter Filters the results, using OData syntax. .PARAMETER Count Includes a count of the matching results in the response. .EXAMPLE PS C:\> Get-MdIndicator Retrieve from Windows Defender ATP list of all active indicators .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Top, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Orderby, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string[]] $Select, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Skip, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Filter, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [boolean] $Count ) process { $__mapping = @{ 'Top' = '$top' 'Orderby' = '$orderby' 'Select' = '$select' 'Skip' = '$skip' 'Filter' = '$filter' 'Count' = '$count' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'indicators' Method = 'get' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function New-MdIndicator { <# .SYNOPSIS Indicators - Submit a new indicator .DESCRIPTION Submit a new indicator Scopes required (delegate auth): Ti.ReadWrite .PARAMETER Title The indicator title .PARAMETER IndicatorType The type of the indicator .PARAMETER Description The indicator description .PARAMETER ExpirationTime The expiration time of the indicator .PARAMETER IndicatorValue The value of the indicator .PARAMETER Severity The severity of the indicator .PARAMETER Application The application associated with the indicator .PARAMETER RecommendedActions Recommended actions for the indicator .PARAMETER Action The action that will be taken if the indicator will be discovered in the organization .EXAMPLE PS C:\> New-MdIndicator -Title $title -Description $description -Action $action Submit a new indicator .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/post-ti-indicator?view=o365-worldwide #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Title, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $IndicatorType, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Description, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $ExpirationTime, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $IndicatorValue, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Severity, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Application, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $RecommendedActions, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Action ) process { $__mapping = @{ 'Title' = 'Title' 'IndicatorType' = 'Indicator type' 'Description' = 'Description' 'ExpirationTime' = 'Expiration time' 'IndicatorValue' = 'Indicator Value' 'Severity' = 'Severity' 'Application' = 'Application' 'RecommendedActions' = 'Recommended Actions' 'Action' = 'Action' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Title','IndicatorType','Description','ExpirationTime','IndicatorValue','Severity','Application','RecommendedActions','Action') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'indicators' Method = 'post' RequiredScopes = 'Ti.ReadWrite' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Remove-MdIndicator { <# .SYNOPSIS Indicators - Delete a single indicator by id .DESCRIPTION Delete a single indicator by indicator id .PARAMETER IndicatorID The identifier of the Indicator to delete .EXAMPLE PS C:\> Remove-MdIndicator -IndicatorID $indicatorid Delete a single indicator by indicator id .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $IndicatorID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'indicators/{IndicatorID}' -Replace '{IndicatorID}',$IndicatorID Method = 'delete' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdInvestigation { <# .SYNOPSIS Actions - Get list of investigation .DESCRIPTION Retrieve from Microsoft Defender ATP the most recent investigations .PARAMETER Top Returns only the first n results. .PARAMETER Orderby Sorts the results. .PARAMETER Select Selects which properties to include in the response, defaults to all. .PARAMETER Skip Skips the first n results. .PARAMETER Filter Filters the results, using OData syntax. .PARAMETER InvestigationID The identifier of the investigation to retrieve .PARAMETER Count Includes a count of the matching results in the response. .EXAMPLE PS C:\> Get-MdInvestigation Retrieve from Microsoft Defender ATP the most recent investigations .EXAMPLE PS C:\> Get-MdInvestigation -InvestigationID $investigationid Retrieve from Microsoft Defender ATP a specific investigation .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Top, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Orderby, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string[]] $Select, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Skip, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Filter, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleInvestigation')] [string] $InvestigationID, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [boolean] $Count ) process { $__mapping = @{ 'Top' = '$top' 'Orderby' = '$orderby' 'Select' = '$select' 'Skip' = '$skip' 'Filter' = '$filter' 'Count' = '$count' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'investigations' Method = 'get' } if ($InvestigationID) { $__param.Path += "/$InvestigationID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdLiveResponseResultDownloadLink { <# .SYNOPSIS Retrieves a specific live response command result by its index. .DESCRIPTION Retrieves a specific live response command result by its index. Scopes required (delegate auth): Machine.LiveResponse .PARAMETER MachineActionID The identifier of the machine action .PARAMETER CommandIndex The index of the live response command to get the results download URI for .EXAMPLE PS C:\> Get-MdLiveResponseResultDownloadLink -MachineActionID $machineactionid -CommandIndex $commandindex Get result download URI for a completed live response command .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-live-response-result?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $MachineActionID, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $CommandIndex ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machineactions/{MachineActionID}/GetLiveResponseResultDownloadLink(index={CommandIndex})' -Replace '{MachineActionID}',$MachineActionID -Replace '{CommandIndex}',$CommandIndex Method = 'get' RequiredScopes = 'Machine.LiveResponse' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineAction { <# .SYNOPSIS Actions - Get list of machine actions .DESCRIPTION Retrieve from Windows Defender ATP the most recent machine actions Scopes required (delegate auth): Machine.Read .PARAMETER Top Returns only the first n results. .PARAMETER Orderby Sorts the results. .PARAMETER Select Selects which properties to include in the response, defaults to all. .PARAMETER Skip Skips the first n results. .PARAMETER MachineActionID The identifier of the machine action to retrieve .PARAMETER Filter Filters the results, using OData syntax. .PARAMETER Count Includes a count of the matching results in the response. .EXAMPLE PS C:\> Get-MdMachineAction -MachineActionID $machineactionid Retrieve from Windows Defender ATP a specific machine action .EXAMPLE PS C:\> Get-MdMachineAction Retrieve from Windows Defender ATP the most recent machine actions .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machineaction-object?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Top, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Orderby, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string[]] $Select, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Skip, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleMachineAction')] [string] $MachineActionID, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Filter, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [boolean] $Count ) process { $__mapping = @{ 'Top' = '$top' 'Orderby' = '$orderby' 'Select' = '$select' 'Skip' = '$skip' 'Filter' = '$filter' 'Count' = '$count' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machineactions' Method = 'get' RequiredScopes = 'Machine.Read' } if ($MachineActionID) { $__param.Path += "/$MachineActionID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineactionGetpackageuri { <# .SYNOPSIS Actions - Get investigation package download URI .DESCRIPTION Get a URI that allows downloading of an investigation package .PARAMETER MachineactionID The ID of the investigation package collection .EXAMPLE PS C:\> Get-MdMachineactionGetpackageuri -MachineactionID $machineactionid Get a URI that allows downloading of an investigation package .LINK <unknown> #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $MachineactionID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machineactions/{MachineactionID}/getPackageUri' -Replace '{MachineactionID}',$MachineactionID Method = 'get' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineactionCancel { <# .SYNOPSIS Actions - Cancel a single machine action .DESCRIPTION Cancel a specific machine action .PARAMETER MachineActionID The identifier of the machine action to cancel .PARAMETER Comment A comment to associate to the machine action cancellation .EXAMPLE PS C:\> Set-MdMachineactionCancel -MachineActionID $machineactionid -Comment $comment Cancel a specific machine action .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $MachineActionID, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machineactions/{MachineActionID}/cancel' -Replace '{MachineActionID}',$MachineActionID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Disable-MdMachineIsolation { <# .SYNOPSIS Undo isolation of a device. .DESCRIPTION Undo isolation of a device. Scopes required (delegate auth): Machine.Isolate .PARAMETER Comment A comment to associate to the unisolation .PARAMETER MachineID The ID of the machine to unisolate .EXAMPLE PS C:\> Disable-MdMachineIsolation -Comment $comment -MachineID $machineid Unisolate a machine from network .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/unisolate-machine?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/unisolate' -Replace '{MachineID}',$MachineID Method = 'post' RequiredScopes = 'Machine.Isolate' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Enable-MdMachineIsolation { <# .SYNOPSIS Isolates a device from accessing external network. .DESCRIPTION Isolates a device from accessing external network. Scopes required (delegate auth): Machine.Isolate .PARAMETER IsolationType Type of the isolation. Allowed values are 'Full' (for full isolation) or 'Selective' (to restrict only limited set of applications from accessing the network) .PARAMETER Comment A comment to associate to the isolation .PARAMETER MachineID The ID of the machine to isolate .EXAMPLE PS C:\> Enable-MdMachineIsolation -Comment $comment -MachineID $machineid Isolate a machine from network .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/isolate-machine?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $IsolationType, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'IsolationType' = 'Isolation Type' 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('IsolationType','Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/isolate' -Replace '{MachineID}',$MachineID Method = 'post' RequiredScopes = 'Machine.Isolate' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachine { <# .SYNOPSIS Machines - Get list of machines .DESCRIPTION Retrieve from Windows Defender ATP the most recent machines Scopes required (delegate auth): Machine.Read .PARAMETER Top Returns only the first n results. .PARAMETER Orderby Sorts the results. .PARAMETER MachineID The identifier of the machine to retrieve .PARAMETER Select Selects which properties to include in the response, defaults to all. .PARAMETER Skip Skips the first n results. .PARAMETER Filter Filters the results, using OData syntax. .PARAMETER Count Includes a count of the matching results in the response. .EXAMPLE PS C:\> Get-MdMachine -MachineID $machineid Retrieve from Windows Defender ATP a specific machine .EXAMPLE PS C:\> Get-MdMachine Retrieve from Windows Defender ATP the most recent machines .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Top, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Orderby, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetSingleMachine')] [Alias('Id')] [string] $MachineID, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string[]] $Select, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [int32] $Skip, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Filter, [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [boolean] $Count ) process { $__mapping = @{ 'Top' = '$top' 'Orderby' = '$orderby' 'Select' = '$select' 'Skip' = '$skip' 'Filter' = '$filter' 'Count' = '$count' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @('Top','Orderby','Select','Skip','Filter','Count') -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines' Method = 'get' RequiredScopes = 'Machine.Read' } if ($MachineID) { $__param.Path += "/$MachineID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineRecommendation { <# .SYNOPSIS Retrieves a collection of security recommendations related to a given device ID. .DESCRIPTION Retrieves a collection of security recommendations related to a given device ID. Scopes required (delegate auth): SecurityRecommendation.Read .PARAMETER MachineID ID of the machine to get recommendations for. .EXAMPLE PS C:\> Get-MdMachineRecommendation -MachineID $machineid Retrieves a collection of security recommendations related to the specified device ID. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-security-recommendations?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/recommendations' -Replace '{MachineID}',$MachineID Method = 'get' RequiredScopes = 'SecurityRecommendation.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineSoftware { <# .SYNOPSIS Retrieves a collection of installed software related to a given device ID. .DESCRIPTION Retrieves a collection of installed software related to a given device ID. Scopes required (delegate auth): Software.Read .PARAMETER MachineID ID of the machine to read the installed software from. .EXAMPLE PS C:\> Get-MdMachineSoftware -MachineID $machineid <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-installed-software?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/software' -Replace '{MachineID}',$MachineID Method = 'get' RequiredScopes = 'Software.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdMachineVulnerability { <# .SYNOPSIS Retrieves a collection of discovered vulnerabilities related to a given device ID. .DESCRIPTION Retrieves a collection of discovered vulnerabilities related to a given device ID. Scopes required (delegate auth): Vulnerability.Read .PARAMETER MachineID ID of the machine to read the detected vulnerabilities from. .EXAMPLE PS C:\> Get-MdMachineVulnerability -MachineID $machineid <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-discovered-vulnerabilities?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/vulnerabilities' -Replace '{MachineID}',$MachineID Method = 'get' RequiredScopes = 'Vulnerability.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineCollectinvestigationpackage { <# .SYNOPSIS Actions - Collect investigation package .DESCRIPTION Collect investigation package from a machine .PARAMETER Comment A comment to associate to the collection .PARAMETER MachineID The ID of the machine to collect the investigation from .EXAMPLE PS C:\> Set-MdMachineCollectinvestigationpackage -Comment $comment -MachineID $machineid Collect investigation package from a machine .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/collectInvestigationPackage' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineOffboard { <# .SYNOPSIS Actions - Offboard machine from Microsoft Defender ATP .DESCRIPTION Offboard machine from Microsoft Defender ATP .PARAMETER Comment A comment to associate to the offboarding action .PARAMETER MachineID The ID of the machine to offboard .EXAMPLE PS C:\> Set-MdMachineOffboard -Comment $comment -MachineID $machineid Offboard machine from Microsoft Defender ATP .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/offboard' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineRestrictcodeexecution { <# .SYNOPSIS Actions - Restrict app execution .DESCRIPTION Restrict execution of all applications on the machine except a predefined set .PARAMETER Comment A comment to associate to the restriction .PARAMETER MachineID The ID of the machine to restrict .EXAMPLE PS C:\> Set-MdMachineRestrictcodeexecution -Comment $comment -MachineID $machineid Restrict execution of all applications on the machine except a predefined set .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/restrictCodeExecution' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineRunantivirusscan { <# .SYNOPSIS Actions - Run antivirus scan .DESCRIPTION Initiate Windows Defender Antivirus scan on a machine .PARAMETER ScanType Type of scan to perform. Allowed values are 'Quick' or 'Full' .PARAMETER Comment A comment to associate to the scan request .PARAMETER MachineID The ID of the machine to scan .EXAMPLE PS C:\> Set-MdMachineRunantivirusscan -Comment $comment -MachineID $machineid Initiate Windows Defender Antivirus scan on a machine .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $ScanType, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'ScanType' = 'Scan Type' 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('ScanType','Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/runAntiVirusScan' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineStartinvestigation { <# .SYNOPSIS Actions - Start automated investigation on a machine .DESCRIPTION Start automated investigation on a machine .PARAMETER Comment A comment to associate to the investigation .PARAMETER MachineID The ID of the machine to investigate .EXAMPLE PS C:\> Set-MdMachineStartinvestigation -Comment $comment -MachineID $machineid Start automated investigation on a machine .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/startInvestigation' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineStopandquarantinefile { <# .SYNOPSIS Actions - Stop and quarantine a file .DESCRIPTION Stop execution of a file on a machine and delete it. .PARAMETER Comment A comment to associate to the restriction removal .PARAMETER MachineID The ID of the machine to unrestrict .EXAMPLE PS C:\> Set-MdMachineStopandquarantinefile -Comment $comment -MachineID $machineid Stop execution of a file on a machine and delete it. .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/StopAndQuarantineFile' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineTag { <# .SYNOPSIS Machines - Tag machine .DESCRIPTION Add or remove a tag to/from a machine .PARAMETER Action The action to perform. Value should be one of 'Add' (to add a tag) or 'Remove' (to remove a tag) .PARAMETER Value The tag to add or remove .PARAMETER MachineID The ID of the machine to which the tag should be added or removed .EXAMPLE PS C:\> Set-MdMachineTag -Action $action -Value $value -MachineID $machineid Add or remove a tag to/from a machine .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Action, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Value, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Action' = 'Action' 'Value' = 'Value' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Action','Value') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/tags' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Set-MdMachineUnrestrictcodeexecution { <# .SYNOPSIS Actions - Remove app execution restriction .DESCRIPTION Enable execution of any application on the machine .PARAMETER Comment A comment to associate to the restriction removal .PARAMETER MachineID The ID of the machine to unrestrict .EXAMPLE PS C:\> Set-MdMachineUnrestrictcodeexecution -Comment $comment -MachineID $machineid Enable execution of any application on the machine .LINK <unknown> #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/unrestrictCodeExecution' -Replace '{MachineID}',$MachineID Method = 'post' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Start-MdMachineLiveResponse { <# .SYNOPSIS Runs a sequence of live response commands on a device .DESCRIPTION Runs a sequence of live response commands on a device Scopes required (delegate auth): Machine.LiveResponse .PARAMETER Commands The live response commands to execute. Example: @{ type = "RunScript" params = @( @{ key = "ScriptName" value = "minidump.ps1" }, @{ key = "Args" value = "OfficeClickToRun" } ) } .PARAMETER Comment A comment to associate to the isolation .PARAMETER MachineID ID of the machine to execute a live response script upon .EXAMPLE PS C:\> Start-MdMachineLiveResponse -Commands $commands -Comment $comment -MachineID $machineid Run live response api commands for a single machine .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-live-response?view=o365-worldwide #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [array] $Commands, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Comment, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $MachineID ) process { $__mapping = @{ 'Commands' = 'Commands' 'Comment' = 'Comment' } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @('Commands','Comment') -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'machines/{MachineID}/runliveresponse' -Replace '{MachineID}',$MachineID Method = 'post' RequiredScopes = 'Machine.LiveResponse' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdRecommendation { <# .SYNOPSIS Retrieves a list of all security recommendations affecting the organization. .DESCRIPTION Retrieves a list of all security recommendations affecting the organization. Scopes required (delegate auth): SecurityRecommendation.Read .PARAMETER RecommendationID ID of the recommendation to retrieve. .EXAMPLE PS C:\> Get-MdRecommendation Lists all security recommendations .EXAMPLE PS C:\> Get-MdRecommendation -RecommendationID $recommendationid <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-all-recommendations?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetRecommendationById')] [Alias('Id')] [string] $RecommendationID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'recommendations' Method = 'get' RequiredScopes = 'SecurityRecommendation.Read' } if ($RecommendationID) { $__param.Path += "/$RecommendationID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdRecommendationMachineReference { <# .SYNOPSIS Retrieves a list of devices associated with the security recommendation. .DESCRIPTION Retrieves a list of devices associated with the security recommendation. Scopes required (delegate auth): SecurityRecommendation.Read .PARAMETER RecommendationID ID of the recommendation for which to retrieve devices. .EXAMPLE PS C:\> Get-MdRecommendationMachineReference -RecommendationID $recommendationid Retrieves a list of devices associated with the security recommendation. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-recommendation-machines?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $RecommendationID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'recommendations/{RecommendationID}/machineReferences' -Replace '{RecommendationID}',$RecommendationID Method = 'get' RequiredScopes = 'SecurityRecommendation.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdRecommendationSoftware { <# .SYNOPSIS Retrieves a security recommendation related to a specific software. .DESCRIPTION Retrieves a security recommendation related to a specific software. Scopes required (delegate auth): SecurityRecommendation.Read .PARAMETER RecommendationID ID of the recommendation for which to retrieve software information. .EXAMPLE PS C:\> Get-MdRecommendationSoftware -RecommendationID $recommendationid Retrieves a security recommendation related to a specific software. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/list-recommendation-software?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $RecommendationID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'recommendations/{RecommendationID}/software' -Replace '{RecommendationID}',$RecommendationID Method = 'get' RequiredScopes = 'SecurityRecommendation.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdRecommendationVulnerability { <# .SYNOPSIS Retrieves a list of vulnerabilities associated with the security recommendation. .DESCRIPTION Retrieves a list of vulnerabilities associated with the security recommendation. Scopes required (delegate auth): Vulnerability.Read .PARAMETER RecommendationID ID of the recommendation for which to retrieve vulnerabilities. .EXAMPLE PS C:\> Get-MdRecommendationVulnerability -RecommendationID $recommendationid Retrieves a list of vulnerabilities associated with the security recommendation. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-recommendation-vulnerabilities?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $RecommendationID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'recommendations/{RecommendationID}/vulnerabilities' -Replace '{RecommendationID}',$RecommendationID Method = 'get' RequiredScopes = 'Vulnerability.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdSoftware { <# .SYNOPSIS Retrieves the organization software inventory. .DESCRIPTION Retrieves the organization software inventory. Scopes required (delegate auth): Software.Read .PARAMETER SoftwareID ID of the software to retrieve. .EXAMPLE PS C:\> Get-MdSoftware -SoftwareID $softwareid <insert description here> .EXAMPLE PS C:\> Get-MdSoftware Lists all security recommendations .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-software?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetProductById')] [Alias('Id')] [string] $SoftwareID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'software' Method = 'get' RequiredScopes = 'Software.Read' } if ($SoftwareID) { $__param.Path += "/$SoftwareID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdSoftwareDistribution { <# .SYNOPSIS Shows the distribution of versions of a software in your organization. .DESCRIPTION Shows the distribution of versions of a software in your organization. Scopes required (delegate auth): Software.Read .PARAMETER SoftwareID ID of the software for which to retrieve distribution data. .EXAMPLE PS C:\> Get-MdSoftwareDistribution -SoftwareID $softwareid Shows the distribution of versions of the specified software in your organization. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-software-ver-distribution?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $SoftwareID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'software/{SoftwareID}/distribution' -Replace '{SoftwareID}',$SoftwareID Method = 'get' RequiredScopes = 'Software.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdSoftwareMachinereference { <# .SYNOPSIS Retrieve a list of device references that has this software installed. .DESCRIPTION Retrieve a list of device references that has this software installed. Scopes required (delegate auth): Software.Read .PARAMETER SoftwareID ID of the software for which to retrieve devices that have it installed. .EXAMPLE PS C:\> Get-MdSoftwareMachinereference -SoftwareID $softwareid Retrieve a list of device references that has this software installed. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines-by-software?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $SoftwareID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'software/{SoftwareID}/machineReferences' -Replace '{SoftwareID}',$SoftwareID Method = 'get' RequiredScopes = 'Software.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdSoftwareVulnerability { <# .SYNOPSIS Retrieve a list of vulnerabilities in the installed software. .DESCRIPTION Retrieve a list of vulnerabilities in the installed software. Scopes required (delegate auth): Vulnerability.Read .PARAMETER SoftwareID ID of the software for which to retrieve devices that have it installed. .EXAMPLE PS C:\> Get-MdSoftwareVulnerability -SoftwareID $softwareid Retrieve a list of vulnerabilities in the installed software. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-vuln-by-software?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $SoftwareID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'software/{SoftwareID}/vulnerabilities' -Replace '{SoftwareID}',$SoftwareID Method = 'get' RequiredScopes = 'Vulnerability.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdVulnerability { <# .SYNOPSIS Retrieves a list of all the vulnerabilities. .DESCRIPTION Retrieves a list of all the vulnerabilities. Scopes required (delegate auth): Vulnerability.Read .PARAMETER VulnerabilityID ID of the vulnerability to retrieve. .EXAMPLE PS C:\> Get-MdVulnerability Retrieves a list of all the vulnerabilities. .EXAMPLE PS C:\> Get-MdVulnerability -VulnerabilityID $vulnerabilityid <insert description here> .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-all-vulnerabilities?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'GetVulnerability')] [Alias('Id')] [string] $VulnerabilityID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'vulnerabilities' Method = 'get' RequiredScopes = 'Vulnerability.Read' } if ($VulnerabilityID) { $__param.Path += "/$VulnerabilityID" } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Get-MdVulnerableMachine { <# .SYNOPSIS Retrieves a list of devices affected by a vulnerability. .DESCRIPTION Retrieves a list of devices affected by a vulnerability. Scopes required (delegate auth): Vulnerability.Read .PARAMETER VulnerabilityID ID of the vulnerability for which to retrieve affected devices. .EXAMPLE PS C:\> Get-MdVulnerableMachine -VulnerabilityID $vulnerabilityid Retrieves a list of devices affected by a vulnerability. .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/get-machines-by-vulnerability?view=o365-worldwide #> [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [Alias('Id')] [string] $VulnerabilityID ) process { $__mapping = @{ } $__param = @{ Body = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Header = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping Path = 'vulnerabilities/{VulnerabilityID}/machineReferences' -Replace '{VulnerabilityID}',$VulnerabilityID Method = 'get' RequiredScopes = 'Vulnerability.Read' } $__param += $PSBoundParameters | ConvertTo-HashTable -Include 'ErrorAction', 'WarningAction', 'Verbose' try { Invoke-DefenderAPIRequest @__param } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Invoke-MSecAdvancedHuntingQuery { <# .SYNOPSIS Execute an advanced hunting query. .DESCRIPTION Execute an advanced hunting query. Requires being connected to the "security" service. To establish a connection, use the "Connect-MdeService" command with the parameter '-Service "security"' Example: Connect-DefenderAPIService -Service security -DeviceCode -ClientID $ClientID -TenantID $TenantID Scopes required (delegate auth): AdvancedHunting.Read .PARAMETER Query The hunting query to execute. .EXAMPLE PS C:\> Invoke-MSecAdvancedHuntingQuery -Query 'DeviceProcessEvents | where InitiatingProcessFileName =~ \"powershell.exe\" | project Timestamp, FileName, InitiatingProcessFileName | order by Timestamp desc | limit 2' Executes the query, searching for the latest two powershell processes .LINK https://docs.microsoft.com/en-us/microsoft-365/security/defender/api-advanced-hunting?view=o365-worldwide #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'default')] [string] $Query ) process { $__mapping = @{ 'Query' = 'Query' } $__body = $PSBoundParameters | ConvertTo-HashTable -Include @('Query') -Mapping $__mapping $__query = $PSBoundParameters | ConvertTo-HashTable -Include @() -Mapping $__mapping $__path = 'advancedhunting/run' Invoke-DefenderAPIRequest -Service 'security' -Path $__path -Method post -Body $__body -Query $__query -RequiredScopes 'AdvancedHunting.Read' | ConvertFrom-AdvancedQuery } } <# This is an example configuration file By default, it is enough to have a single one of them, however if you have enough configuration settings to justify having multiple copies of it, feel totally free to split them into multiple files. #> <# # Example Configuration Set-PSFConfig -Module 'DefenderAPI' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" #> Set-PSFConfig -Module 'DefenderAPI' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'DefenderAPI' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." Set-PSFConfig -Module 'DefenderAPI' -Name 'Request.SerializationDepth' -Value 99 -Initialize -Validation integerpositive -Description 'How deep provided request bodies are serialized.' <# Stored scriptblocks are available in [PsfValidateScript()] attributes. This makes it easier to centrally provide the same scriptblock multiple times, without having to maintain it in separate locations. It also prevents lengthy validation scriptblocks from making your parameter block hard to read. Set-PSFScriptblock -Name 'DefenderAPI.ScriptBlockName' -Scriptblock { } #> # Available Tokens $script:_DefenderTokens = @{} # Endpoint Configuration for Requests $script:_DefenderEndpoints = @{} $script:_strings = Get-PSFLocalizedString -Module DefenderAPI # Registers the default service configurations $endpointCfg = @{ Name = 'Endpoint' ServiceUrl = 'https://api.securitycenter.microsoft.com/api' Resource = 'https://api.securitycenter.microsoft.com' DefaultScopes = @() Header = @{ 'Content-Type' = 'application/json' } HelpUrl = 'https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/api/apis-intro?view=o365-worldwide' } Register-DefenderAPIService @endpointCfg $securityCfg = @{ Name = 'Security' ServiceUrl = 'https://api.security.microsoft.com/api' Resource = 'https://security.microsoft.com/mtp/' DefaultScopes = @('AdvancedHunting.Read') Header = @{ 'Content-Type' = 'application/json' } HelpUrl = 'https://learn.microsoft.com/en-us/microsoft-365/security/defender/api-create-app-web?view=o365-worldwide' } Register-DefenderAPIService @securityCfg New-PSFLicense -Product 'DefenderAPI' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2024-03-11") -Text @" Copyright (c) 2024 Friedrich Weinmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ #endregion Load compiled code |