Will discover LicenseSku objects.
Will discover the M365 License objects.
None.
Name | Description | Default Value |
IntervalSeconds | The frequncy of the workflow. | 900 |
PoshLibraryPath | For customer support engineer use only. | |
ProbeActionTimeoutSeconds | If the workflow module does not exit gracefully by this time limit, the module will be forced to terminate. | ProbeActionTimeoutSecondsDEFAULT |
SyncTime | This can be set to force a workflow to synchronize its Interval to a specific start time. If no SyncTime value is provided to the workflow, the workflow will be initiated at the agent's earliest opportunity after receiving a configuration change or restarting. Typically no SyncTime is preferred. Format is 00:00. Example for 5:36pm: 17:36. Example for 2:15am: 02:15. | |
WriteToEventLog | This will enable/disable script logging to the Operations Manager event log. | false |
Type | DataSourceModuleType |
Isolation | Any |
Accessibility | Public |
RunAs | M365SL.RunAs.Profile |
OutputType | System.Discovery.Data |
ID | Module Type | TypeId | RunAs |
---|---|---|---|
DS | DataSource | Microsoft.Windows.TimedPowerShell.DiscoveryProvider | Default |
ID | ParameterType | Selector |
---|---|---|
IntervalSeconds | int | $Config/IntervalSeconds$ |
PoshLibraryPath | string | $Config/PoshLibraryPath$ |
ProbeActionTimeoutSeconds | int | $Config/ProbeActionTimeoutSeconds$ |
SkuNamesFilePath | string | $Config/SkuNamesFilePath$ |
SyncTime | string | $Config/SyncTime$ |
WriteToEventLog | bool | $Config/WriteToEventLog$ |
<DataSourceModuleType ID="M365SLIC.License.POSH.Discovery.DS" Accessibility="Public" Batching="false" RunAs="M365SL!M365SL.RunAs.Profile">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ApiTokenScopeURL" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ApiTokenURL" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ApiURL" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="IntervalSeconds" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="MgmtGroupRegKey" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="PoshLibraryPath" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="PrincipalName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ProbeActionTimeoutSeconds" minOccurs="1" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SkuNamesFilePath" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SyncTime" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TenantName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TLSVersion" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WorkflowName" minOccurs="1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="WriteToEventLog" minOccurs="1" type="xsd:boolean"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="PoshLibraryPath" Selector="$Config/PoshLibraryPath$" ParameterType="string"/>
<OverrideableParameter ID="ProbeActionTimeoutSeconds" Selector="$Config/ProbeActionTimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="SkuNamesFilePath" Selector="$Config/SkuNamesFilePath$" ParameterType="string"/>
<OverrideableParameter ID="SyncTime" Selector="$Config/SyncTime$" ParameterType="string"/>
<OverrideableParameter ID="WriteToEventLog" Selector="$Config/WriteToEventLog$" ParameterType="bool"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider">
<IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
<SyncTime>$Config/SyncTime$</SyncTime>
<ScriptName>M365SLIC.LicenseDiscovery.ps1</ScriptName>
<ScriptBody> <#
Filename: M365SLIC.LicenseDiscovery.ps1
Author: Brian Zoucha, Stephen Mccommas, Taylour Blackwell, Tyson Paul
Description: Instance discovery for Microsoft 365 Service License Management and Monitoring.
Version History:
2020.11.17.2316 - Added API Url params
2020.10.13.1809 - Updated discovery for M365SLIC.License.Role to reflect service model change.
2020.09.30.1630 - Added Role class.
2020.09.29.1442 - Modified License service model.
2020.09.28.1442 - Added TLS param
2020.09.24.1554 - Updated Load-Library
2020.09.18.0000 - v1
#>
Param (
[string]$ApiTokenScopeURL,
[string]$ApiTokenURL,
[string]$ApiURL,
[string]$ManagedEntityID = "No ManagedEntityID defined",
[string]$MgmtGroupRegKey,
[string]$PoshLibraryPath, #comma-separated list of .ps1 files to load
[string]$PrincipalName = "No PrincipalName defined!",
[string]$SkuNamesFilePath,
[string]$SourceID = "No SourceID defined",
[string]$TenantName,
[string]$TLSVersion,
[string]$WorkflowName = '<No Name Provided>',
[string]$WriteToEventLog="false"
)
[bool]$WriteToEventLog = [System.Convert]::ToBoolean($WriteToEventLog)
[string]$ScriptName = 'M365SLIC.LicenseDiscovery.ps1'
$NameSpace = 'License'
$Testing = $false
######################### FUNCTIONS ############################
################################################################
Function Load-Library {
Param (
[string]$PoshLibraryPath
)
$ErrorActionPreference = 'STOP'
If ($PoshLibraryPath ){
ForEach ($Path in $PoshLibraryPath.Split(',') ){
Try {
If (($Path.Length) -AND ($Path -notmatch '^-1$')) {
. $Path
}
} Catch {
Write-Host "Line [$($MyInvocation.ScriptLineNumber )]: Error loading PoshLibrary at path:[$($Path)]. This is likely to cause many other dependent functions to fail. `n`nError data: $($_)`n`n"
}
}
}
$ErrorActionPreference = 'CONTINUE'
}
################################################################
############## TESTING ##############
<# #Run this as needed when testing
# powershell.exe -file C:\Test\M365SMP_Dev\SharePoint\M365SSPO.SharePointDiscovery.ps1 > C:\Test\M365SMP_Dev\DiscoveryData.xml
#---------------------------------------
Function Testing {
$Testing = $true
$testParamsFile = (Join-path $TestFolder ("PARAMS_$($ScriptName)"))
#Write-Host "$(Test-Path $testParamsFile):$($testParamsFile)" -F Yellow -B Green
. $testParamsFile
. Load-Library -PoshLibraryPath $PoshLibraryPath
# Encode user data/passwords in current test user context
$M365_ClientSecret = Encode-UserData $M365_ClientSecret_PLAINTEXT
$M365_AccountPassword = Encode-UserData $M365_AccountPassword_PLAINTEXT
$error.Clear()
}
#---------------------------------------
$TestFolder = "C:\Test\M365SMP_Dev\$($NameSpace)\TestSetup"
If (Test-Path -Path $TestFolder) {
. Testing
}
#>
############## TESTING ##############
. Load-Library -PoshLibraryPath $PoshLibraryPath
LogIt -EventID 9990 -Type $info -Msg "Begin script..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
LogIt -EventID 9992 -Type $info -msg "Setting TLS for session, Version: [$($TLSVersion)]." -Proceed $WriteToEventLog -Line $(_LINE_); $Error.Clear();
#Set Default TLS
. Set-TLS -TLSVersion $TLSVersion
$TenantKey = Join-Path $MgmtGroupRegKey $TenantName
$ThisKey = Join-Path $TenantKey $NameSpace
$TenantKeyPath = $TenantKey.Replace("HKLM\",'HKLM:\')
$RegKeyPath = $ThisKey.Replace("HKLM\",'HKLM:\')
$TenantProperties = Get-ItemProperty -Path ($TenantKey.Replace("HKLM\",'HKLM:\') )
LogIt -EventID 9992 -Type $info -Msg "TenantKeyPath: [$($TenantKeyPath)], KeyPath: [$($RegKeyPath)]" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
$api = New-Object -comObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceID, $ManagedEntityID)
Try {
# Verify if the Reg key exists
If (Test-Path $RegKeyPath ) {
$RegProperties = (Get-ItemProperty -Path $RegKeyPath -ErrorAction Stop)
# Verify if properties exists (Data/Values)
If (-NOT [bool]$RegProperties) {
$discoveryData
LogIt -EventID 9995 -Type $warn -Msg "RegKey does exist:[$($ThisKey)] but no properties found. This is unusual; if the key exists, properties/data should exist also. Exiting Discovery workflow: [$WorkflowName]. " -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
}
}
Else {throw}
} Catch {
# Return empty discovery data. This will undiscover entities if they were previously discovered.
$discoveryData
LogIt -EventID 9992 -Type $info -Msg "RegKey does not exist:[$($ThisKey)] or possible error accessing registry data value(s). Exiting Discovery workflow: [$WorkflowName]. " -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Exit
}
If ($Testing) {
$RegProperties.M365_AccountName = $M365_AccountName
$RegProperties.M365_ClientID = $M365_ClientId
$RegProperties.M365_AccountPassword = Encode-UserData -Data $M365_AccountPassword_PLAINTEXT
$RegProperties.M365_ClientSecret = Encode-UserData -Data $M365_ClientSecret_PLAINTEXT
}
# Decode Account Password
LogIt -EventID 9992 -Type $info -Msg "Decode password..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$M365_AccountPassword_DECRYPTED = Decode-UserData -Data $RegProperties.M365_AccountPassword
} Catch {
LogIt -EventID 9995 -Type $warn -Msg "Unable to decode password. See error data. " -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
# Decode Client Secret
LogIt -EventID 9992 -Type $info -Msg "Decode ClientSecret..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$M365_ClientSecret_DECRYPTED = Decode-UserData -Data $RegProperties.M365_ClientSecret
} Catch {
LogIt -EventID 9995 -Type $warn -Msg "Unable to decode ClientSecret. See error data. " -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
# Get Access Token
LogIt -EventID 9992 -Type $info -Msg "Get Access Token..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$TokenResponse = Get-AccessToken -Delegated -User $RegProperties.M365_AccountName -Pass $M365_AccountPassword_DECRYPTED -ClientID $RegProperties.M365_ClientID -ClientSecret $M365_ClientSecret_DECRYPTED -TenantName $TenantName -ApiTokenUrl $ApiTokenURL -ApiTokenScopeURL $ApiTokenScopeURL
} Catch {
LogIt -EventID 9995 -Type $warn -Msg "Unable to retrieve Access Token. See error data. " -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
LogIt -EventID 9992 -Type $info -Msg "Retrieve License object(s)" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Try {
$LicenseUrl = "$($ApiURL)/v1.0/subscribedSkus"
$Licenses = Invoke-RestMethod -Headers @{Authorization = "Bearer $($TokenResponse.access_token)"} -Uri $LicenseUrl -Method Get -ContentType 'application/json' -ErrorAction Stop
LogIt -EventID 9992 -Type $info -Msg "Retrieved License object(s): [$($Licenses.Value.skuPartNumber | Out-String)]" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
} Catch {
LogIt -EventID 9995 -Type $warn -Msg "Unable to get License object(s). See error data." -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
If (-NOT ($Licenses.Value.Count)) {
LogIt -EventID 9992 -Type $info -Msg "No License objects exist. Will not UNdiscover any registered objects. Exiting Discovery workflow: [$WorkflowName]. " -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Exit
}
<# Ability to UNdiscovery License objects is not used at this time
If ((-NOT ($Licenses.Value.Count)) -AND (-NOT $Error.Count)) {
# Return empty discovery data. This will undiscover entities if they were previously discovered.
$discoveryData
LogIt -EventID 9992 -Type $info -Msg "No License objects exist. Will undiscover any previously discovered objects. Exiting Discovery workflow: [$WorkflowName]. " -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
Exit
}
#>
#region IfLicensesExist
Else {
#Discover M365SLIC.License.Role
Try {
LogIt -EventID 9992 -Type $info -Msg "Proceed to discovery type: [M365SLIC.License.Role] for tenant: [$($TenantName)]..." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
$instance = $discoveryData.CreateClassInstance('$MPElement[Name="M365SLIC.License.Role"]$')
# Add instance properties
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/IntervalSeconds$", [int]($RegProperties.IntervalSeconds ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_AccountName$", [string]($RegProperties.M365_AccountName ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_AccountPassword$", [string]($RegProperties.M365_AccountPassword ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_ClientID$", [string]($RegProperties.M365_ClientID ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_ClientSecret$", [string]($RegProperties.M365_ClientSecret ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/TenantName$", [string]($TenantName) )
# Host
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.WatcherNode']/TenantName$", [string]($TenantName) )
# Standard Props
$instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $PrincipalName)
$instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $TenantName)
$discoveryData.AddInstance($instance)
LogIt -EventID 9992 -Type $info -Msg "Discovery object type: [M365SLIC.License.Role] for tenant: [$($TenantName)] added to instance data item!" -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
} Catch {
$msg = "There was a critical failure in discovery type: [M365SLIC.License.Role] for tenant at path: [$($TenantKey)]"
LogIt -EventID 9995 -Type $Critical -Msg $msg -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
#region Try to load friendly DisplayNames
$hashNames = @{}
Try {
$ObjNames = Import-Csv -Path $SkuNamesFilePath | Sort-Object -ErrorAction Stop
# Build hash table of ugly-to-friendly name associations.
ForEach ($item in $ObjNames) {
$hashNames[($item.skuPartNumber)] = $item.DisplayName
}
} Catch {
LogIt -EventID 9992 -Type $info -Msg "Problem trying to import Sku DisplayNames from path: [$($SkuNamesFilePath)]." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
# No friendly DisplayNames could be found. No choice but to build hash table of ugly-to-ugly name associations.
ForEach ($item in ($Licenses.Value.skuPartNumber)) {
$hashNames[($item)] = $item
}
}
#endregion
#region ForEachSku
ForEach ($objSku in $Licenses.Value) {
Try {
LogIt -EventID 9992 -Type $info -Msg "Properties exist at this Key: [$($ThisKey)]. Proceed to discover this class type: [M365SLIC.License.Sku] for TenantName: [$($TenantName)]." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
If (-NOT ($hashNames[($objSku.skuPartNumber)])) {
LogIt -EventID 9992 -Type $info -Msg "No friendly name exists for Sku: [$($objSku.skuPartNumber)]." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
$hashNames[($objSku.skuPartNumber)] = $objSku.skuPartNumber
}
# Declare instance
$instance = $discoveryData.CreateClassInstance('$MPElement[Name="M365SLIC.License.Sku"]$')
# Parent
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/IntervalSeconds$", [int]($RegProperties.IntervalSeconds ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_AccountName$", [string]($RegProperties.M365_AccountName ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_AccountPassword$", [string]($RegProperties.M365_AccountPassword ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_ClientID$", [string]($RegProperties.M365_ClientID ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/M365_ClientSecret$", [string]($RegProperties.M365_ClientSecret ) )
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.M365ServiceComponent']/TenantName$", [string]($TenantName) )
# Add instance properties (M365 license Sku object)
# Key
$instance.AddProperty("$MPElement[Name='M365SLIC.License.Sku']/SkuPartNumber$", ($objSku.SkuPartNumber) )
$instance.AddProperty("$MPElement[Name='M365SLIC.License.Sku']/SkuID$", ($objSku.SkuID) )
# Host
$instance.AddProperty("$MPElement[Name='M365SL!M365SL.WatcherNode']/TenantName$", [string]($TenantName) )
# Standard Props
$instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $PrincipalName)
$instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $hashNames[($objSku.SkuPartNumber)] )
$discoveryData.AddInstance($instance)
LogIt -EventID 9992 -Type $info -Msg "Added discovery instance data for M365SLIC.License.Sku SkuPartNumber: [$($objSku.SkuPartNumber)], tenant: [$($TenantName)]." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
}
Catch {
$msg = "There was a critical failure in discovery for M365SLIC.License.Sku SkuPartNumber: [$($objSku.SkuPartNumber)], tenant: [$($TenantName)]."
LogIt -EventID 9995 -Type $Critical -Msg $msg -Proceed $true -LINE $(_LINE_); $Error.Clear()
}
}
#endregion ForEachSku
} #endregion IfLicensesExist
LogIt -EventID 9992 -Type $info -Msg "Return License discovery data for tenant: [$($TenantName)]." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear()
$discoveryData
If ($Testing) {
$api.Return($discoveryData)
}
LogIt -EventID 9991 -Type $info -Msg "End script." -Proceed $WriteToEventLog -LINE $(_LINE_); $Error.Clear() </ScriptBody>
<Parameters>
<Parameter>
<Name>ApiTokenScopeURL</Name>
<Value>$Config/ApiTokenScopeURL$</Value>
</Parameter>
<Parameter>
<Name>ApiTokenURL</Name>
<Value>$Config/ApiTokenURL$</Value>
</Parameter>
<Parameter>
<Name>ApiURL</Name>
<Value>$Config/ApiURL$</Value>
</Parameter>
<Parameter>
<Name>ManagedEntityId</Name>
<Value>$Target/Id$</Value>
</Parameter>
<Parameter>
<Name>MgmtGroupRegKey</Name>
<Value>$Config/MgmtGroupRegKey$</Value>
</Parameter>
<Parameter>
<Name>PoshLibraryPath</Name>
<Value>$FileResource[Name='Res.M365SLIC.M365Library.ps1.Resource']/Path$,$Config/PoshLibraryPath$</Value>
</Parameter>
<Parameter>
<Name>PrincipalName</Name>
<Value>$Config/PrincipalName$</Value>
</Parameter>
<Parameter>
<Name>SkuNamesFilePath</Name>
<Value>$Config/SkuNamesFilePath$</Value>
</Parameter>
<Parameter>
<Name>SourceId</Name>
<Value>$MPElement$</Value>
</Parameter>
<Parameter>
<Name>TenantName</Name>
<Value>$Config/TenantName$</Value>
</Parameter>
<Parameter>
<Name>TLSVersion</Name>
<Value>$Config/TLSVersion$</Value>
</Parameter>
<Parameter>
<Name>WorkflowName</Name>
<Value>$Config/WorkflowName$</Value>
</Parameter>
<Parameter>
<Name>WriteToEventLog</Name>
<Value>$Config/WriteToEventLog$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>$Config/ProbeActionTimeoutSeconds$</TimeoutSeconds>
</DataSource>
</MemberModules>
<Composition>
<Node ID="DS"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.Discovery.Data</OutputType>
</DataSourceModuleType>