diff --git a/CHANGELOG.md b/CHANGELOG.md index 0390105429..b25d25321c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ * IntuneDeviceConfigurationSharedMultiDevicePolicyWindows10 * Add missing `AccessTokens` parameter to `Export-TargetResource` FIXES [#5034](https://github.com/microsoft/Microsoft365DSC/issues/5034) +* SCInsiderRiskEntityList + * Initial release. * SCRoleGroup * Fixes an issue with creation without specifying Displayname * Fixes an issue with Drifts because of returned Role format @@ -47,6 +49,7 @@ * SPOAccessControlSettings * Added support for property EnableRestrictedAccessControl. * DEPENDENCIES + * Updated Microsoft.Graph to version 2.23.0. * Added dependencies on Az.Accounts, Az.Resources and Az.SecurityInsights * Updated DSCParser to version 2.0.0.9. * Updated MSCloudLoginAssistant to version 1.1.25. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.psm1 new file mode 100644 index 0000000000..a0d5e92ed3 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.psm1 @@ -0,0 +1,1260 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ListType, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Domains, + + [Parameter()] + [System.String[]] + $FilePaths, + + [Parameter()] + [System.String[]] + $FileTypes, + + [Parameter()] + [System.String[]] + $Keywords, + + [Parameter()] + [System.String[]] + $SensitiveInformationTypes, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Sites, + + [Parameter()] + [System.String[]] + $TrainableClassifiers, + + [Parameter()] + [System.String[]] + $ExceptionKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedClassifierGroups, + + [Parameter()] + [System.String[]] + $ExcludedDomainGroups, + + [Parameter()] + [System.String[]] + $ExcludedFilePathGroups, + + [Parameter()] + [System.String[]] + $ExcludedFileTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedSensitiveInformationTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedSiteGroups, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + ##TODO - Replace the workload by the one associated to your resource + New-M365DSCConnection -Workload 'SecurityComplianceCenter' ` + -InboundParameters $PSBoundParameters | Out-Null + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $nullResult = $PSBoundParameters + $nullResult.Ensure = 'Absent' + try + { + $instance = Get-InsiderRiskEntityList -Identity $Name -ErrorAction Stop + + if ($null -eq $instance) + { + return $nullResult + } + + # CustomDomainLists + $DmnValues = @() + if ($instance.ListType -eq 'CustomDomainLists' -or ` + $instance.Name -eq 'IrmWhitelistDomains') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $current = @{ + Dmn = $entity.Dmn + isMLSubDmn = $entity.isMLSubDmn + } + $DmnValues += $current + } + } + + # CustomFilePathRegexLists + $FilePathValues = @() + if ($instance.ListType -eq 'CustomFilePathRegexLists' -or ` + $instance.Name -eq 'IrmCustomExWinFilePaths') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $FilePathValues += $entity.FlPthRgx + } + } + + # CustomFileTypeLists + $FileTypeValues = @() + if ($instance.ListType -eq 'CustomFileTypeLists') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $FileTypeValues += $entity.Ext + } + } + + # CustomKeywordLists + $KeywordValues = @() + if ($instance.ListType -eq 'CustomKeywordLists' -or ` + $instance.Name -eq 'IrmExcludedKeywords' -or $instance.Name -eq 'IrmNotExcludedKeywords') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $KeywordValues += $entity.Name + } + } + + # CustomSensitiveInformationTypeLists + $SITValues = @() + if ($instance.ListType -eq 'CustomSensitiveInformationTypeLists' -or ` + $instance.Name -eq 'IrmCustomExSensitiveTypes') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $SITObject = Get-DLPSensitiveInformationType -Identity $entity.GUID + $SITValues += $SITObject.Name + } + } + + # CustomSiteLists + $SiteValues = @() + if ($instance.ListType -eq 'CustomSiteLists' -or ` + $instance.Name -eq 'IrmExcludedSites') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $site = @{ + Url = $entity.Url + Name = $entity.Name + Guid = $entity.Guid + } + $SiteValues += $site + } + } + + # CustomMLClassifierTypeLists + $TrainableClassifierValues = @() + if ($instance.ListType -eq 'CustomMLClassifierTypeLists' -or $instance.Name -eq 'IrmCustomExMLClassifiers') + { + foreach ($entity in $instance.Entities) + { + $entity = ConvertFrom-Json $entity + $TrainableClassifierValues += $entity.Guid + } + } + + # Global Exclusions - Excluded Keyword Groups + $excludedKeywordGroupValue = @() + if ($instance.Name -eq 'IrmXSGExcludedKeywords') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedKeywordGroupValue += $group.Name + } + } + + # Global Exclusions - Exception Keyword Groups + $exceptionKeywordGroupValue = @() + if ($instance.Name -eq 'IrmXSGExceptionKeywords') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $exceptionKeywordGroupValue += $group.Name + } + } + + # Global Exclusions - Excluded Classifier Groups + $excludedClassifierGroupValue = @() + if ($instance.Name -eq 'IrmXSGMLClassifierTypes') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedClassifierGroupValue += $group.Name + } + } + + # Global Exclusions - Excluded Domain Groups + $excludedDomainGroupValue = @() + if ($instance.Name -eq 'IrmXSGDomains') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedDomainGroupValue += $group.Name + } + } + + # Global Exclusions - Excluded File Path Groups + $ExcludedFilePathGroupsValue = @() + if ($instance.Name -eq 'IrmXSGFilePaths') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $ExcludedFilePathGroupsValue += $group.Name + } + } + + # Global Exclusions - Excluded Site Groups + $excludedSiteGroupValue = @() + if ($instance.Name -eq 'IrmXSGSites') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedSiteGroupValue += $group.Name + } + } + + # Global Exclusions - Excluded Sensitive Info Type Groups + $excludedSITGroupValue = @() + if ($instance.Name -eq 'IrmXSGSensitiveInfoTypes') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedSITGroupValue += $group.Name + } + } + + # Global Exclusions - Excluded File Type Groups + $excludedFileTypeGroupValue = @() + if ($instance.Name -eq 'IrmXSGFiletypes') + { + $entities = $instance.Entities + foreach ($entity in $entities) + { + $entity = ConvertFrom-Json $entity + $group = Get-InsiderRiskEntityList -Identity $entity.GroupId + $excludedFileTypeGroupValue += $group.Name + } + } + + $results = @{ + DisplayName = $instance.DisplayName + Name = $instance.Name + Description = $instance.Description + ListType = $instance.ListType + Domains = $DmnValues + FilePaths = $FilePathValues + FileTypes = $FileTypeValues + Keywords = $KeywordValues + SensitiveInformationTypes = $SITValues + Sites = $SiteValues + TrainableClassifiers = $TrainableClassifierValues + ExcludedKeyworkGroups = $excludedKeywordGroupValue + ExceptionKeyworkGroups = $exceptionKeywordGroupValue + ExcludedClassifierGroups = $excludedClassifierGroupValue + ExcludedDomainGroups = $excludedDomainGroupValue + ExcludedFilePathGroups = $ExcludedFilePathGroupsValue + ExcludedSiteGroups = $excludedSiteGroupValue + ExcludedSensitiveInformationTypeGroups = $excludedSITGroupValue + ExcludedFileTypeGroups = $excludedFileTypeGroupValue + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + return [System.Collections.Hashtable] $results + } + catch + { + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ListType, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Domains, + + [Parameter()] + [System.String[]] + $FilePaths, + + [Parameter()] + [System.String[]] + $FileTypes, + + [Parameter()] + [System.String[]] + $Keywords, + + [Parameter()] + [System.String[]] + $SensitiveInformationTypes, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Sites, + + [Parameter()] + [System.String[]] + $TrainableClassifiers, + + [Parameter()] + [System.String[]] + $ExceptionKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedClassifierGroups, + + [Parameter()] + [System.String[]] + $ExcludedDomainGroups, + + [Parameter()] + [System.String[]] + $ExcludedFilePathGroups, + + [Parameter()] + [System.String[]] + $ExcludedFileTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedSensitiveInformationTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedSiteGroups, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $currentInstance = Get-TargetResource @PSBoundParameters + + # CREATE + if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') + { + # Create a new Domain Group + if ($ListType -eq 'CustomDomainLists') + { + $value = @() + foreach ($domain in $Domains) + { + $value += "{`"Dmn`":`"$($domain.Dmn)`",`"isMLSubDmn`":$($domain.isMLSubDmn.ToString().ToLower())}" + } + Write-Verbose -Message "Creating new Domain Group {$Name} with values {$($value -join ',')}" + New-InsiderRiskEntityList -Type 'CustomDomainLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomFilePathRegexLists') + { + $value = @() + foreach ($filePath in $FilePaths) + { + $value += "{`"FlPthRgx`":`"$($filePath.Replace('\', '\\'))`",`"isSrc`":true,`"isTrgt`":true}" + } + Write-Verbose -Message "Creating new FilePath Group {$Name} with values {$($value -join ',')}" + New-InsiderRiskEntityList -Type 'CustomFilePathRegexLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomFileTypeLists') + { + $value = @() + foreach ($fileType in $FileTypes) + { + $value += "{`"Ext`":`"$fileType`"}" + } + Write-Verbose -Message "Creating new FileType Group {$Name} with values {$($value -join ',')}" + New-InsiderRiskEntityList -Type 'CustomFileTypeLists ' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomKeywordLists') + { + $value = @() + foreach ($keyword in $Keywords) + { + $value += "{`"Name`":`"$keyword`"}" + } + Write-Verbose -Message "Creating new Keyword Group {$Name} with values {$($value -join ',')}" + New-InsiderRiskEntityList -Type 'CustomKeywordLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomSensitiveInformationTypeLists') + { + $value = @() + foreach ($sit in $SensitiveInformationTypes) + { + $value += "{`"Guid`":`"$sit`"}" + } + Write-Verbose -Message "Creating new SIT Group {$Name} with values {$($value -join ',')}" + New-InsiderRiskEntityList -Type 'CustomSensitiveInformationTypeLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomSiteLists') + { + $value = @() + foreach ($site in $Sites) + { + $value += "{`"Url`":`"$($site.Url.ToString())`",`"Name`":`"$($site.Name.ToString())`",`"Guid`":`"$((New-GUID).ToString())`"}" + } + Write-Verbose -Message "Creating new Site Group {$Name} with values {$($value)}" + New-InsiderRiskEntityList -Type 'CustomSiteLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + elseif ($ListType -eq 'CustomMLClassifierTypeLists') + { + $value = @() + foreach ($clasifier in $TrainableClassifiers) + { + $value += "{`"Guid`":`"$($classifier)`"}" + } + Write-Verbose -Message "Creating new Trainable classifier Group {$Name} with values {$($value)}" + New-InsiderRiskEntityList -Type 'CustomMLClassifierTypeLists' ` + -Name $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -Entities $value | Out-Null + } + else + { + throw "Couldn't not identify operation to perform on {$Name}" + } + } + # UPDATE + elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') + { + # Update Domain Group + if ($ListType -eq 'CustomDomainLists' -or $Name -eq 'IrmWhitelistDomains') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.Domains.Dmn -DifferenceObject $Domains.Dmn + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $instance = $Domains | Where-Object -FilterScript {$_.Dmn -eq $diff.InputObject} + $entitiesToAdd += "{`"Dmn`":`"$($instance.Dmn)`",`"isMLSubDmn`":$($instance.isMLSubDmn.ToString().ToLower())}" + } + else + { + $instance = $currentInstance.Domains | Where-Object -FilterScript {$_.Dmn -eq $diff.InputObject} + $entitiesToRemove += "{`"Dmn`":`"$($instance.Dmn)`",`"isMLSubDmn`":$($instance.isMLSubDmn.ToString().ToLower())}" + } + } + + Write-Verbose -Message "Updating Domain Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update File Path Group + elseif ($ListType -eq 'CustomFilePathRegexLists' -or $Name -eq 'IrmCustomExWinFilePaths' -or ` + $Name -eq 'IrmDsbldSysExWinFilePaths') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.FilePaths -DifferenceObject $FilePaths + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"FlPthRgx`":`"$($diff.InputObject.Replace('\', '\\'))`",`"isSrc`":true,`"isTrgt`":true}" + } + else + { + $entitiesToRemove += "{`"FlPthRgx`":`"$($diff.InputObject.Replace('\', '\\'))`",`"isSrc`":true,`"isTrgt`":true}" + } + } + + Write-Verbose -Message "Updating File Path Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update File Type Group + elseif ($ListType -eq 'CustomFileTypeLists') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.FileTypes -DifferenceObject $FileTypes + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"Ext`":`"$($diff.InputObject)`"}" + } + else + { + $entitiesToRemove += "{`"Ext`":`"$($diff.InputObject)`"}" + } + } + + Write-Verbose -Message "Updating File Type Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update Keywords Group + elseif ($ListType -eq 'CustomKeywordLists' -or $Name -eq 'IrmExcludedKeywords' -or $Name -eq 'IrmNotExcludedKeywords') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.Keywords -DifferenceObject $Keywords + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"Name`":`"$($diff.InputObject)`"}" + } + else + { + $entitiesToRemove += "{`"Name`":`"$($diff.InputObject)`"}" + } + } + + Write-Verbose -Message "Updating Keyword Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update SIT Group + elseif ($ListType -eq 'CustomSensitiveInformationTypeLists' -or $Name -eq 'IrmCustomExSensitiveTypes ' -or ` + $Name -eq 'IrmDsbldSysExSensitiveTypes') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.SensitiveInformationTypes -DifferenceObject $SensitiveInformationTypes + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"Guid`":`"$($diff.InputObject)`"}" + } + else + { + $entitiesToRemove += "{`"Guid`":`"$($diff.InputObject)`"}" + } + } + + Write-Verbose -Message "Updating SIT Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update Sites Group + elseif ($ListType -eq 'CustomSiteLists' -or $Name -eq 'IrmExcludedSites') + { + Write-Verbose -Message "Calculating the difference in the Site list." + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.Sites.Url -DifferenceObject $Sites.Url + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entry = $Sites | Where-Object -FilterScript {$_.Url -eq $diff.InputObject} + $guid = $entry.Guid + if ([System.String]::IsNullOrEmpty($guid)) + { + $guid = (New-Guid).ToString() + } + $entitiesToAdd += "{`"Url`":`"$($entry.Url)`",`"Name`":`"$($entry.Name)`",`"Guid`":`"$($guid)`"}" + } + else + { + $entry = $currentInstance.Sites | Where-Object -FilterScript {$_.Url -eq $diff.InputObject} + $entitiesToRemove += "{`"Url`":`"$($entry.Url)`",`"Name`":`"$($entry.Name)`",`"Guid`":`"$($entry.Guid)`"}" + } + } + + Write-Verbose -Message "Updating Sites Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + # Update Trainable Classifiers Group + elseif ($ListType -eq 'CustomMLClassifierTypeLists' -or $Name -eq 'IrmCustomExMLClassifiers' -or ` + $Name -eq 'IrmDsbldSysExMLClassifiers') + { + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $currentInstance.Sites.Url -DifferenceObject $Sites.Url + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"Guid`":`"$($diff.InputObject)`"}" + } + else + { + $entitiesToRemove += "{`"Guid`":`"$($diff.InputObject)`"}" + } + } + + Write-Verbose -Message "Updating Sites Group {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -DisplayName $DisplayName ` + -Description $Description ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null + } + + <################## Group Exclusions #############> + if ($null -ne $ExcludedDomainGroups -and $ExcludedDomainGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedDomainGroups ` + -DesiredValues $ExcludedDomainGroups ` + -Name 'IrmXSGDomains' + } + elseif ($null -ne $ExcludedFilePathGroups -and $ExcludedFilePathGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedFilePathGroups ` + -DesiredValues $ExcludedFilePathGroups ` + -Name 'IrmXSGFilePaths' + } + elseif ($null -ne $ExcludedFileTypeGroups -and $ExcludedFileTypeGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedFileTypeGroups ` + -DesiredValues $ExcludedFileTypeGroups ` + -Name 'IrmXSGFiletypes' + } + elseif ($null -ne $ExceptionKeyworkGroups -and $ExceptionKeyworkGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExceptionKeyworkGroups ` + -DesiredValues $ExceptionKeyworkGroups ` + -Name 'IrmXSGExcludedKeywords ' + } + elseif ($null -ne $ExcludedKeyworkGroups -and $ExcludedKeyworkGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedKeyworkGroups ` + -DesiredValues $ExcludedKeyworkGroups ` + -Name 'IrmXSGExcludedKeywords ' + } + elseif ($null -ne $ExcludedSensitiveInformationTypeGroups -and $ExcludedSensitiveInformationTypeGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedSensitiveInformationTypeGroups ` + -DesiredValues $ExcludedSensitiveInformationTypeGroups ` + -Name 'IrmXSGSensitiveInfoTypes ' + } + elseif ($null -ne $ExcludedSiteGroups -and $ExcludedSiteGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedSiteGroups ` + -DesiredValues $ExcludedSiteGroups ` + -Name 'IrmXSGSites ' + } + elseif ($null -ne $ExcludedClassifierGroups -and $ExcludedClassifierGroups.Length -gt 0) + { + Set-M365DSCSCInsiderRiskExclusionGroup -CurrentValues $currentInstance.ExcludedClassifierGroups ` + -DesiredValues $ExcludedClassifierGroups ` + -Name 'IrmXSGMLClassifierTypes ' + } + } + # REMOVE + elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') + { + Write-Verbose -Message "Removing group {$Name}" + Remove-InsiderRiskEntityList -Identity $Name -ForceDeletion + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [System.String] + $ListType, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Domains, + + [Parameter()] + [System.String[]] + $FilePaths, + + [Parameter()] + [System.String[]] + $FileTypes, + + [Parameter()] + [System.String[]] + $Keywords, + + [Parameter()] + [System.String[]] + $SensitiveInformationTypes, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Sites, + + [Parameter()] + [System.String[]] + $TrainableClassifiers, + + [Parameter()] + [System.String[]] + $ExceptionKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedClassifierGroups, + + [Parameter()] + [System.String[]] + $ExcludedDomainGroups, + + [Parameter()] + [System.String[]] + $ExcludedFilePathGroups, + + [Parameter()] + [System.String[]] + $ExcludedFileTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedKeyworkGroups, + + [Parameter()] + [System.String[]] + $ExcludedSensitiveInformationTypeGroups, + + [Parameter()] + [System.String[]] + $ExcludedSiteGroups, + + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $CurrentValues = Get-TargetResource @PSBoundParameters + $ValuesToCheck = ([Hashtable]$PSBoundParameters).Clone() + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $ValuesToCheck)" + + $testResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck $ValuesToCheck.Keys + + Write-Verbose -Message "Test-TargetResource returned $testResult" + + return $testResult +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + $ConnectionMode = New-M365DSCConnection -Workload 'SecurityComplianceCenter' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + try + { + $Script:ExportMode = $true + [array] $Script:exportedInstances = @() + $availableTypes = @('HveLists', 'DomainLists', 'CriticalAssetLists', 'WindowsFilePathRegexLists', 'SensitiveTypeLists', 'SiteLists', 'KeywordLists', ` + 'CustomDomainLists', 'CustomSiteLists', 'CustomKeywordLists', 'CustomFileTypeLists', 'CustomFilePathRegexLists', ` + 'CustomSensitiveInformationTypeLists', 'CustomMLClassifierTypeLists', 'GlobalExclusionSGMapping', 'DlpPolicyLists') + + # Retrieve entries for each type + foreach ($listType in $availableTypes) + { + $Script:exportedInstances += Get-InsiderRiskEntityList -Type $listType -ErrorAction Stop + } + + $i = 1 + $dscContent = '' + if ($Script:exportedInstances.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + foreach ($config in $Script:exportedInstances) + { + if ($null -ne $Global:M365DSCExportResourceInstancesCount) + { + $Global:M365DSCExportResourceInstancesCount++ + } + $displayedKey = $config.ListType + ' - ' + $config.Name + Write-Host " |---[$i/$($Script:exportedInstances.Count)] $displayedKey" -NoNewline + $params = @{ + DisplayName = $config.DisplayName + Name = $config.Name + ListType = $config.ListType + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + $Results = Get-TargetResource @Params + + if ($null -ne $Results.Domains -and $Results.Domains.Length -gt 0 -and ` + ($Results.ListType -eq 'CustomDomainLists' -or $Results.ListType -eq 'DomainLists')) + { + $Results.Domains = ConvertTo-M365DSCSCInsiderRiskDomainToString -Domains $Results.Domains + } + + if ($null -ne $Results.Sites -and $Results.Sites.Length -gt 0 -and ` + ($Results.ListType -eq 'CustomSiteLists' -or $Results.ListType -eq 'SiteLists')) + { + $Results.Sites = ConvertTo-M365DSCSCInsiderRiskSiteToString -Sites $Results.Sites + } + + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + + if ($null -ne $Results.Domains -and ` + ($Results.ListType -eq 'CustomDomainLists' -or $Results.ListType -eq 'DomainLists')) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Domains' -IsCIMArray $true + } + + if ($null -ne $Results.Sites -and ` + ($Results.ListType -eq 'CustomSiteLists' -or $Results.ListType -eq 'SiteLists')) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Sites' -IsCIMArray $true + } + + $dscContent += $currentDSCBlock + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $i++ + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + return $dscContent + } + catch + { + Write-Host $Global:M365DSCEmojiRedX + + New-M365DSCLogEntry -Message 'Error during Export:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return '' + } +} + +function ConvertTo-M365DSCSCInsiderRiskDomainToString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory=$true)] + [System.Object[]] + $Domains + ) + + $content = "@(" + foreach ($domain in $Domains) + { + $content += "MSFT_SCInsiderRiskEntityListDomain`r`n" + $content += "{`r`n" + $content += " Dmn = '$($domain.Dmn)'`r`n" + $content += " isMLSubDmn = `$$($domain.isMLSubDmn)`r`n" + $content += "}`r`n" + } + $content += ")" + return $content +} + +function ConvertTo-M365DSCSCInsiderRiskSiteToString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory=$true)] + [System.Object[]] + $Sites + ) + + $content = "@(" + foreach ($site in $Sites) + { + $content += "MSFT_SCInsiderRiskEntityListSite`r`n" + $content += "{`r`n" + $content += " Url = '$($site.Url)'`r`n" + $content += " Name = '$($site.Name)'`r`n" + $content += " Guid = '$($site.Guid)'`r`n" + $content += "}`r`n" + } + $content += ")" + return $content +} + +function Set-M365DSCSCInsiderRiskExclusionGroup +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.String[]] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.String[]] + $DesiredValues, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $entitiesToAdd = @() + $entitiesToRemove = @() + $differences = Compare-Object -ReferenceObject $CurrentValues -DifferenceObject $DesiredValues + foreach ($diff in $differences) + { + if ($diff.SideIndicator -eq '=>') + { + $entitiesToAdd += "{`"GroupId`":`"$($diff.InputObject)`"}" + } + else + { + $entitiesToRemove += "{`"GroupId`":`"$($diff.InputObject)`"}" + } + } + + Write-Verbose -Message "Updating Group Exclusions for {$Name}" + Write-Verbose -Message "Adding entities: $($entitiesToAdd -join ',')" + Write-Verbose -Message "Removing entities: $($entitiesToRemove -join ',')" + + Set-InsiderRiskEntityList -Identity $Name ` + -AddEntities $entitiesToAdd ` + -RemoveEntities $entitiesToRemove | Out-Null +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.schema.mof new file mode 100644 index 0000000000..020a45d679 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/MSFT_SCInsiderRiskEntityList.schema.mof @@ -0,0 +1,43 @@ +[ClassVersion("1.0.0")] +class MSFT_SCInsiderRiskEntityListDomain +{ + [Required, Description("Domain name.")] String Dmn; + [Write, Description("Defines if the entry should include multi-level subdomains or not.")] Boolean isMLSubDmn; +}; +[ClassVersion("1.0.0")] +class MSFT_SCInsiderRiskEntityListSite +{ + [Required, Description("Url of the site.")] String Url; + [Write, Description("Name of the site.")] String Name; + [Write, Description("Unique identifier of the site.")] String Guid; +}; +[ClassVersion("1.0.0.0"), FriendlyName("SCInsiderRiskEntityList")] +class MSFT_SCInsiderRiskEntityList : OMI_BaseResource +{ + [Key, Description("The name of the group or setting.")] String Name; + [Required, Description("The setting type.")] String ListType; + [Write, Description("Description for the group or setting.")] String Description; + [Write, Description("The display name of the group or setting.")] String DisplayName; + [Write, Description("List of domains"), EmbeddedInstance("MSFT_SCInsiderRiskEntityListDomain")] String Domains[]; + [Write, Description("List of file paths.")] String FilePaths[]; + [Write, Description("List of file types.")] String FileTypes[]; + [Write, Description("List of keywords.")] String Keywords[]; + [Write, Description("List of sensitive information types.")] String SensitiveInformationTypes[]; + [Write, Description("List of sites."), EmbeddedInstance("MSFT_SCInsiderRiskEntityListSite")] String Sites[]; + [Write, Description("List of trainable classifiers.")] String TrainableClassifiers[]; + [Write, Description("List of keywords for exception.")] String ExceptionKeyworkGroups[]; + [Write, Description("List of excluded trainable classifiers.")] String ExcludedClassifierGroups[]; + [Write, Description("List of excluded domains.")] String ExcludedDomainGroups[]; + [Write, Description("List of excluded file paths.")] String ExcludedFilePathGroups[]; + [Write, Description("List of excluded file types.")] String ExcludedFileTypeGroups[]; + [Write, Description("List of excluded keywords.")] String ExcludedKeyworkGroups[]; + [Write, Description("List of excluded sensitive information types.")] String ExcludedSensitiveInformationTypeGroups[]; + [Write, Description("List of excluded sites.")] String ExcludedSiteGroups[]; + [Write, Description("Specify if this entity should exist or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write, Description("Credentials of the workload's Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Write, Description("Managed ID being used for authentication.")] Boolean ManagedIdentity; + [Write, Description("Access token used for authentication.")] String AccessTokens[]; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/readme.md new file mode 100644 index 0000000000..75a6e455f5 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/readme.md @@ -0,0 +1,6 @@ + +# SCInsiderRiskEntityList + +## Description + +Configures settings for Insider Risk in Purview. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/settings.json new file mode 100644 index 0000000000..98c729187d --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCInsiderRiskEntityList/settings.json @@ -0,0 +1,20 @@ +{ + "resourceName": "SCInsiderRiskEntityList", + "description": "Configures settings for Insider Risk in Purview.", + "roles": { + "read": [], + "update": [] + }, + "permissions": { + "graph": { + "delegated": { + "read": [], + "update": [] + }, + "application": { + "read": [], + "update": [] + } + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/1-Create.ps1 new file mode 100644 index 0000000000..b1d724763e --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/1-Create.ps1 @@ -0,0 +1,38 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + SCInsiderRiskEntityList "SCInsiderRiskEntityList-MyFileType" + { + ApplicationId = $ApplicationId; + CertificateThumbprint = $CertificateThumbprint; + Description = "Test file type"; + DisplayName = "MyFileType"; + Ensure = "Present"; + FileTypes = @(".exe",".cmd",".bat"); + Keywords = @(); + ListType = "CustomFileTypeLists"; + Name = "MyFileTypeList"; + TenantId = $OrganizationName; + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/2-Update.ps1 new file mode 100644 index 0000000000..c230194a30 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/2-Update.ps1 @@ -0,0 +1,38 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + SCInsiderRiskEntityList "SCInsiderRiskEntityList-MyFileType" + { + ApplicationId = $ApplicationId; + CertificateThumbprint = $CertificateThumbprint; + Description = "Test file type"; + DisplayName = "MyFileType"; + Ensure = "Present"; + FileTypes = @(".exe",".txt",".bat"); # Drfit + Keywords = @(); + ListType = "CustomFileTypeLists"; + Name = "MyFileTypeList"; + TenantId = $OrganizationName; + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/3-Remove.ps1 b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/3-Remove.ps1 new file mode 100644 index 0000000000..cf49044588 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/SCInsiderRiskEntityList/3-Remove.ps1 @@ -0,0 +1,38 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + SCInsiderRiskEntityList "SCInsiderRiskEntityList-MyFileType" + { + ApplicationId = $ApplicationId; + CertificateThumbprint = $CertificateThumbprint; + Description = "Test file type"; + DisplayName = "MyFileType"; + Ensure = "Absent"; + FileTypes = @(".exe",".cmd",".bat"); + Keywords = @(); + ListType = "CustomFileTypeLists"; + Name = "MyFileTypeList"; + TenantId = $OrganizationName; + } + } +} diff --git a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 index e10606ca0c..2545a03b39 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 @@ -1169,7 +1169,7 @@ function Compare-M365DSCConfigurations This function gets the key parameter for the specified CIMInstance .Functionality -Internal, Hidden +Public #> function Get-M365DSCCIMInstanceKey { @@ -1223,6 +1223,18 @@ function Get-M365DSCCIMInstanceKey { $primaryKey = 'dataType' } + elseif ($CIMInstance.ContainsKey("Dmn")) + { + $primaryKey = 'Dmn' + } + elseif ($CIMInstance.ContainsKey('EmergencyDialString')) + { + $primaryKey = 'EmergencyDialString' + } + else + { + $primaryKey = $CIMInstance.Keys[0] + } return $primaryKey } @@ -1931,5 +1943,6 @@ function Initialize-M365DSCReporting Export-ModuleMember -Function @( 'Compare-M365DSCConfigurations', 'New-M365DSCDeltaReport', - 'New-M365DSCReportFromConfiguration' + 'New-M365DSCReportFromConfiguration', + 'Get-M365DSCCIMInstanceKey' ) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCTelemetryEngine.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCTelemetryEngine.psm1 index a62b0907b7..a393a98017 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCTelemetryEngine.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCTelemetryEngine.psm1 @@ -368,7 +368,10 @@ function Add-M365DSCTelemetryEvent # LCM Metadata Information try { - $LCMInfo = Get-DscLocalConfigurationManager -ErrorAction Stop + if ($null -eq $Script:LCMInfo) + { + $Script:LCMInfo = Get-DscLocalConfigurationManager -ErrorAction Stop + } $certificateConfigured = $false if (-not [System.String]::IsNullOrEmpty($LCMInfo.CertificateID)) @@ -377,17 +380,17 @@ function Add-M365DSCTelemetryEvent } $partialConfiguration = $false - if (-not [System.String]::IsNullOrEmpty($LCMInfo.PartialConfigurations)) + if (-not [System.String]::IsNullOrEmpty($Script:LCMInfo.PartialConfigurations)) { $partialConfiguration = $true } $Data.Add('LCMUsesPartialConfigurations', $partialConfiguration) $Data.Add('LCMCertificateConfigured', $certificateConfigured) - $Data.Add('LCMConfigurationMode', $LCMInfo.ConfigurationMode) - $Data.Add('LCMConfigurationModeFrequencyMins', $LCMInfo.ConfigurationModeFrequencyMins) - $Data.Add('LCMRefreshMode', $LCMInfo.RefreshMode) - $Data.Add('LCMState', $LCMInfo.LCMState) - $Data.Add('LCMStateDetail', $LCMInfo.LCMStateDetail) + $Data.Add('LCMConfigurationMode', $Script:LCMInfo.ConfigurationMode) + $Data.Add('LCMConfigurationModeFrequencyMins', $Script:LCMInfo.ConfigurationModeFrequencyMins) + $Data.Add('LCMRefreshMode', $Script:LCMInfo.RefreshMode) + $Data.Add('LCMState', $Script:LCMInfo.LCMState) + $Data.Add('LCMStateDetail', $Script:LCMInfo.LCMStateDetail) if ([System.String]::IsNullOrEmpty($Type)) { @@ -395,18 +398,18 @@ function Add-M365DSCTelemetryEvent { $Type = 'Export' } - elseif ($LCMInfo.LCMStateDetail -eq 'LCM is performing a consistency check.' -or ` - $LCMInfo.LCMStateDetail -eq 'LCM exécute une vérification de cohérence.' -or ` - $LCMInfo.LCMStateDetail -eq 'LCM führt gerade eine Konsistenzüberprüfung durch.') + elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is performing a consistency check.' -or ` + $Script:LCMInfo.LCMStateDetail -eq 'LCM exécute une vérification de cohérence.' -or ` + $Script:LCMInfo.LCMStateDetail -eq 'LCM führt gerade eine Konsistenzüberprüfung durch.') { $Type = 'MonitoringScheduled' } - elseif ($LCMInfo.LCMStateDetail -eq 'LCM is testing node against the configuration.') + elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is testing node against the configuration.') { $Type = 'MonitoringManual' } - elseif ($LCMInfo.LCMStateDetail -eq 'LCM is applying a new configuration.' -or ` - $LCMInfo.LCMStateDetail -eq 'LCM applique une nouvelle configuration.') + elseif ($Script:LCMInfo.LCMStateDetail -eq 'LCM is applying a new configuration.' -or ` + $Script:LCMInfo.LCMStateDetail -eq 'LCM applique une nouvelle configuration.') { $Type = 'ApplyingConfiguration' } diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index b498f41a3f..d899a833d1 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -462,7 +462,7 @@ function Compare-PSCustomObjectArrays Desired = $DesiredEntry.$KeyProperty Current = $null } - $DriftedProperties += $DesiredEntry + $DriftedProperties += $result } else { @@ -496,6 +496,62 @@ function Compare-PSCustomObjectArrays } } + foreach ($currentEntry in $currentValues) + { + if ($currentEntry.GetType().Name -eq 'PSCustomObject') + { + $fixedEntry = @{} + $currentEntry.psobject.properties | Foreach { $fixedEntry[$_.Name] = $_.Value } + } + else + { + $fixedEntry = $currentEntry + } + $KeyProperty = Get-M365DSCCIMInstanceKey -CIMInstance $fixedEntry + + $EquivalentEntryInDesired = $DesiredValues | Where-Object -FilterScript { $_.$KeyProperty -eq $fixedEntry.$KeyProperty } + if ($null -eq $EquivalentEntryInDesired) + { + $result = @{ + Property = $fixedEntry + PropertyName = $KeyProperty + Desired = $fixedEntry.$KeyProperty + Current = $null + } + $DriftedProperties += $result + } + else + { + foreach ($property in $Properties) + { + $propertyName = $property.Name + + if ((-not [System.String]::IsNullOrEmpty($fixedEntry.$PropertyName) -and -not [System.String]::IsNullOrEmpty($EquivalentEntryInDesired.$PropertyName)) -and ` + $fixedEntry.$PropertyName -ne $EquivalentEntryInDesired.$PropertyName) + { + $drift = $true + if ($fixedEntry.$PropertyName.GetType().Name -eq 'String' -and $fixedEntry.$PropertyName.Contains('$OrganizationName')) + { + if ($fixedEntry.$PropertyName.Split('@')[0] -eq $EquivalentEntryInDesired.$PropertyName.Split('@')[0]) + { + $drift = $false + } + } + if ($drift) + { + $result = @{ + Property = $fixedEntry + PropertyName = $PropertyName + Desired = $fixedEntry.$PropertyName + Current = $EquivalentEntryInDesired.$PropertyName + } + $DriftedProperties += $result + } + } + } + } + } + return $DriftedProperties } @@ -700,8 +756,20 @@ function Test-M365DSCParameterState } $AllDesiredValuesAsArray += [PSCustomObject]$currentEntry } - $arrayCompare = Compare-PSCustomObjectArrays -CurrentValues $CurrentValues.$fieldName ` - -DesiredValues $AllDesiredValuesAsArray + try + { + $arrayCompare = $null + if ($CurrentValues.$fieldName.GetType().Name -ne 'CimInstance' -and ` + $CurrentValues.$fieldName.GetType().Name -ne 'CimInstance[]') + { + $arrayCompare = Compare-PSCustomObjectArrays -CurrentValues $CurrentValues.$fieldName ` + -DesiredValues $AllDesiredValuesAsArray + } + } + catch + { + Write-Verbose -Message $_ + } if ($null -ne $arrayCompare) { @@ -3669,7 +3737,7 @@ function Get-M365DSCExportContentForResource { $primaryKey = '' } - elseif ($Keys.Contains('DisplayName')) + elseif ($Keys.Contains('DisplayName') -and -not [System.String]::IsNullOrEmpty($Results.DisplayName)) { $primaryKey = $Results.DisplayName } diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SCInsiderRiskEntityList.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SCInsiderRiskEntityList.Tests.ps1 new file mode 100644 index 0000000000..8fb7fa383d --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SCInsiderRiskEntityList.Tests.ps1 @@ -0,0 +1,225 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath '..\..\Unit' ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Microsoft365.psm1' ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Generic.psm1' ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\UnitTestHelper.psm1' ` + -Resolve) + +$CurrentScriptPath = $PSCommandPath.Split('\') +$CurrentScriptName = $CurrentScriptPath[$CurrentScriptPath.Length -1] +$ResourceName = $CurrentScriptName.Split('.')[1] +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource $ResourceName -GenericStubModule $GenericStubPath + +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + BeforeAll { + + $secpasswd = ConvertTo-SecureString (New-Guid | Out-String) -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + + Mock -CommandName Confirm-M365DSCDependencies -MockWith { + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credentials" + } + + Mock -CommandName Set-InsiderRiskEntityList -MockWith { + } + + Mock -CommandName New-InsiderRiskEntityList -MockWith { + } + + Mock -CommandName Remove-InsiderRiskEntityList -MockWith { + } + + # Mock Write-Host to hide output during the tests + Mock -CommandName Write-Host -MockWith { + } + $Script:exportedInstances =$null + $Script:ExportMode = $false + } + # Test contexts + Context -Name "The instance should exist but it DOES NOT" -Fixture { + BeforeAll { + $testParams = @{ + Description = "Test Description"; + DisplayName = "TestFileTypeList"; + Ensure = "Present"; + FileTypes = @(".exe",".cmd",".bat"); + ListType = "CustomFileTypeLists"; + Name = "TestName"; + Credential = $Credential; + } + + Mock -CommandName Get-InsiderRiskEntityList -MockWith { + return $null + } + } + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' + } + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should create a new instance from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName New-InsiderRiskEntityList -Exactly 1 + } + } + + Context -Name "The instance exists but it SHOULD NOT" -Fixture { + BeforeAll { + $testParams = @{ + Description = "Test Description"; + DisplayName = "TestFileTypeList"; + Ensure = "Absent"; + FileTypes = @(".exe",".cmd",".bat"); + ListType = "CustomFileTypeLists"; + Name = "TestName"; + Credential = $Credential; + } + + Mock -CommandName Get-InsiderRiskEntityList -MockWith { + return @{ + ListType = 'CustomFileTypeLists' + Name = 'TestName'; + DisplayName = "TestFileTypeList"; + Description = "Test Description"; + Entities = @( + '{"Ext":".exe"}', + '{"Ext":".cmd"}', + '{"Ext":".bat"}' + ) + } + } + } + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should remove the instance from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Remove-InsiderRiskEntityList -Exactly 1 + } + } + + Context -Name "The instance exists and values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + Description = "Test Description"; + DisplayName = "TestFileTypeList"; + Ensure = "Present"; + FileTypes = @(".exe",".cmd",".bat"); + ListType = "CustomFileTypeLists"; + Name = "TestName"; + Credential = $Credential; + } + + Mock -CommandName Get-InsiderRiskEntityList -MockWith { + return @{ + ListType = 'CustomFileTypeLists' + Name = 'TestName'; + DisplayName = "TestFileTypeList"; + Description = "Test Description"; + Entities = @( + '{"Ext":".exe"}', + '{"Ext":".cmd"}', + '{"Ext":".bat"}' + ) + } + } + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The instance exists and values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + Description = "Test Description"; + DisplayName = "TestFileTypeList"; + Ensure = "Present"; + FileTypes = @(".exe",".cmd",".bat"); + ListType = "CustomFileTypeLists"; + Name = "TestName"; + Credential = $Credential; + } + + Mock -CommandName Get-InsiderRiskEntityList -MockWith { + return @{ + ListType = 'CustomFileTypeLists' + Name = 'TestName'; + DisplayName = "TestFileTypeList"; + Description = "Test Description"; + Entities = @( + '{"Ext":".exe"}', + '{"Ext":".txt"}', #drift + '{"Ext":".bat"}' + ) + } + } + } + + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Set-InsiderRiskEntityList -Exactly 1 + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential; + } + + Mock -CommandName Get-InsiderRiskEntityList -MockWith { + return @{ + ListType = 'CustomFileTypeLists' + Name = 'TestName'; + DisplayName = "TestFileTypeList"; + Description = "Test Description"; + Entities = @( + '{"Ext":".exe"}', + '{"Ext":".cmd"}', + '{"Ext":".bat"}' + ) + } + } + } + It 'Should Reverse Engineer resource from the Export method' { + $result = Export-TargetResource @testParams + $result | Should -Not -BeNullOrEmpty + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SPOUserProfileProperty.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SPOUserProfileProperty.Tests.ps1 index 64b8e2e90c..d082a08989 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SPOUserProfileProperty.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.SPOUserProfileProperty.Tests.ps1 @@ -101,7 +101,11 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Get-PnPUserProfileProperty -MockWith { return @{ AccountName = 'john.smith@contoso.com' - UserProfileProperties = @{'MyOldKey' = 'MyValue' } + UserProfileProperties = @( + @{ + MyOldKey = 'MyValue' + } + ) } } } diff --git a/Tests/Unit/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index 36fb0879a9..b9278ef7cc 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -70030,6 +70030,98 @@ function Set-SupervisoryReviewPolicy $SamplingRate ) } +function Set-InsiderRiskEntityList +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Identity, + + [Parameter()] + [System.Object[]] + $Entities, + + [Parameter()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.Object[]] + $AddEntities, + + [Parameter()] + [System.Object[]] + $RemoveEntities + ) +} + +function New-InsiderRiskEntityList +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Identity, + + [Parameter()] + [System.Object[]] + $Entities, + + [Parameter()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Type + ) +} + +function Remove-InsiderRiskEntityList +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Identity, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ForceDeletion + ) +} + +function Get-InsiderRiskEntityList +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Identity, + + [Parameter()] + [System.String] + $Type + ) +} + function Set-SupervisoryReviewPolicyV2 { [CmdletBinding()] diff --git a/dev-package/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ResourceName.Tests.ps1 b/dev-package/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ResourceName.Tests.ps1 index 20857e0393..f3ddc7d594 100644 --- a/dev-package/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ResourceName.Tests.ps1 +++ b/dev-package/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ResourceName.Tests.ps1 @@ -66,6 +66,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { It 'Should create a new instance from the Set method' { ##TODO - Replace the New-Cmdlet by the appropriate one + Set-TargetResource @testParams Should -Invoke -CommandName New-Cmdlet -Exactly 1 } }