diff --git a/CHANGELOG.md b/CHANGELOG.md index a42713a14c..165117b676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ FIXES [#3028](https://github.com/microsoft/Microsoft365DSC/issues/3028) * IntuneEndpointDetectionAndResponsePolicyWindows10 * Migrate to new Settings Catalog cmdlets. +* IntuneMobileAppsMacOSLobApp + * Initial release * M365DSCDRGUtil * Fixes an issue for the handling of skipped one-property elements in the Settings Catalog. FIXES [#5086](https://github.com/microsoft/Microsoft365DSC/issues/5086) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.psm1 new file mode 100644 index 0000000000..664ccca695 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.psm1 @@ -0,0 +1,1138 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + #region Intune resource parameters + + [Parameter()] + [System.String] + $Id, + + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.Boolean] + $IgnoreVersionDetection, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds, + + [Parameter()] + [System.String] + $BundleId, + + [Parameter()] + [System.String] + $BuildNumber, + + [Parameter()] + [System.String] + $VersionNumber, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $ChildApps, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + #endregion + + [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()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -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-MgBetaDeviceAppManagementMobileApp ` + -Filter "(isof('microsoft.graph.macOSLobApp') and displayName eq '$DisplayName')" ` + -ExpandProperty "categories,assignments" ` + -ErrorAction SilentlyContinue | Where-Object ` + -FilterScript { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.macOSLobApp' } + + if ($null -eq $instance) + { + Write-Verbose -Message "No Mobile app with DisplayName {$DisplayName} was found. Search with DisplayName." + $instance = Get-MgBetaDeviceAppManagementMobileApp ` + -MobileAppId $Id ` + -ExpandProperty "categories,assignments" ` + -ErrorAction Stop | Where-Object ` + -FilterScript { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.macOSLobApp' } + } + + if ($null -eq $instance) + { + Write-Verbose -Message "No Mobile app with {$Id} was found." + return $nullResult + } + + $results = @{ + Id = $instance.Id + Description = $instance.Description + Developer = $instance.Developer + DisplayName = $instance.DisplayName + InformationUrl = $instance.InformationUrl + IsFeatured = $instance.IsFeatured + Notes = $instance.Notes + Owner = $instance.Owner + PrivacyInformationUrl = $instance.PrivacyInformationUrl + Publisher = $instance.Publisher + PublishingState = $instance.PublishingState.ToString() + RoleScopeTagIds = $instance.RoleScopeTagIds + BundleId = $instance.BundleId + BuildNumber = $instance.BuildNumber + VersionNumber = $instance.VersionNumber + IgnoreVersionDetection = $instance.AdditionalProperties.ignoreVersionDetection + + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + ApplicationSecret = $ApplicationSecret + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + #region complex types + + #Categories + if($null -ne $instance.Categories) + { + $results.Add('Categories', $instance.Categories) + } + else { + $results.Add('Categories', "") + } + + #childApps + if($null -ne $instance.AdditionalProperties.childApps) + { + $results.Add('ChildApps', $instance.AdditionalProperties.childApps) + } + else { + $results.Add('ChildApps', "") + } + + #Assignments + $resultAssignments = @() + $appAssignments = Get-MgBetaDeviceAppManagementMobileAppAssignment -MobileAppId $instance.Id + if ($null -ne $appAssignments -and $appAssignments.count -gt 0) + { + $resultAssignments += ConvertFrom-IntuneMobileAppAssignment ` + -IncludeDeviceFilter:$true ` + -Assignments ($appAssignments) + + $results.Add('Assignments', $resultAssignments) + } + + #LargeIcon + # The large is returned only when Get cmdlet is called with Id parameter. The large icon is a base64 encoded string, so we need to convert it to a byte array. + $instanceWithLargeIcon = Get-MgBetaDeviceAppManagementMobileApp -MobileAppId $instance.Id + if($null -ne $instanceWithLargeIcon.LargeIcon) + { + $results.Add('LargeIcon', $instanceWithLargeIcon.LargeIcon) + } + else { + $results.Add('LargeIcon', "") + } + + #end region complex types + + return [System.Collections.Hashtable] $results + } + catch + { + Write-Verbose -Message $_ + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + #region Intune resource parameters + + [Parameter()] + [System.String] + $Id, + + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.Boolean] + $IgnoreVersionDetection, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds, + + [Parameter()] + [System.String] + $BundleId, + + [Parameter()] + [System.String] + $BuildNumber, + + [Parameter()] + [System.String] + $VersionNumber, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $ChildApps, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + #endregion + + [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()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [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 + $PSBoundParameters.Remove('Ensure') | Out-Null + $PSBoundParameters.Remove('Credential') | Out-Null + $PSBoundParameters.Remove('ApplicationId') | Out-Null + $PSBoundParameters.Remove('ApplicationSecret') | Out-Null + $PSBoundParameters.Remove('TenantId') | Out-Null + $PSBoundParameters.Remove('CertificateThumbprint') | Out-Null + $PSBoundParameters.Remove('ManagedIdentity') | Out-Null + $PSBoundParameters.Remove('AccessTokens') | Out-Null + + $CreateParameters = Remove-M365DSCAuthenticationParameter -BoundParameters $PSBoundParameters + + # CREATE + if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') + { + Write-Host "Create MacOS app: $DisplayName" + + $CreateParameters = ([Hashtable]$PSBoundParameters).clone() + $CreateParameters = Rename-M365DSCCimInstanceParameter -Properties $CreateParameters + + $AdditionalProperties = Get-M365DSCIntuneMobileMocOSLobAppAdditionalProperties -Properties ($CreateParameters) + foreach ($key in $AdditionalProperties.keys) + { + if ($key -ne '@odata.type') + { + $keyName = $key.substring(0, 1).ToUpper() + $key.substring(1, $key.length - 1) + $CreateParameters.remove($keyName) + } + } + + $CreateParameters.remove('Id') | Out-Null + $CreateParameters.remove('Ensure') | Out-Null + $CreateParameters.remove('Categories') | Out-Null + $CreateParameters.remove('Assignments') | Out-Null + $CreateParameters.remove('childApps') | Out-Null + $CreateParameters.remove('IgnoreVersionDetection') | Out-Null + $CreateParameters.Remove('Verbose') | Out-Null + $CreateParameters.Remove('PublishingState') | Out-Null #Not allowed to update as it's a computed property + $CreateParameters.Remove('LargeIcon') | Out-Null + + foreach ($key in ($CreateParameters.clone()).Keys) + { + if ($CreateParameters[$key].getType().Fullname -like '*CimInstance*') + { + $CreateParameters[$key] = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $CreateParameters[$key] + } + } + + if ($AdditionalProperties) + { + $CreateParameters.add('AdditionalProperties', $AdditionalProperties) + } + + #LargeIcon + if($LargeIcon) + { + [System.Object]$LargeIconValue = ConvertTo-M365DSCIntuneAppLargeIcon -LargeIcon $LargeIcon + $CreateParameters.Add('LargeIcon', $LargeIconValue) + } + + $app = New-MgBetaDeviceAppManagementMobileApp @CreateParameters + + #Assignments + $assignmentsHash = ConvertTo-IntuneMobileAppAssignment -IncludeDeviceFilter:$true -Assignments $Assignments + if ($app.id) + { + Update-MgBetaDeviceAppManagementMobileAppAssignment -MobileAppId $app.id ` + -Target $assignmentsHash ` + -Repository 'deviceAppManagement/mobileAppAssignments' + } + } + # UPDATE + elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') + { + Write-Host "Update MacOS app: $DisplayName" + + $PSBoundParameters.Remove('Assignments') | Out-Null + $UpdateParameters = ([Hashtable]$PSBoundParameters).clone() + $UpdateParameters = Rename-M365DSCCimInstanceParameter -Properties $UpdateParameters + + $AdditionalProperties = Get-M365DSCIntuneMobileMocOSLobAppAdditionalProperties -Properties ($UpdateParameters) + foreach ($key in $AdditionalProperties.keys) + { + if ($key -ne '@odata.type') + { + $keyName = $key.substring(0, 1).ToUpper() + $key.substring(1, $key.length - 1) + #Remove additional keys, so that later they can be added as 'AdditionalProperties' + $UpdateParameters.Remove($keyName) + } + } + + $UpdateParameters.Remove('Id') | Out-Null + $UpdateParameters.Remove('Verbose') | Out-Null + $UpdateParameters.Remove('Categories') | Out-Null + $UpdateParameters.Remove('PublishingState') | Out-Null #Not allowed to update as it's a computed property + + foreach ($key in ($UpdateParameters.clone()).Keys) + { + if ($UpdateParameters[$key].getType().Fullname -like '*CimInstance*') + { + $value = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $UpdateParameters[$key] + $UpdateParameters[$key] = $value + } + } + + if ($AdditionalProperties) + { + $UpdateParameters.Add('AdditionalProperties', $AdditionalProperties) + } + + #LargeIcon + if($LargeIcon) + { + [System.Object]$LargeIconValue = ConvertTo-M365DSCIntuneAppLargeIcon -LargeIcon $LargeIcon + $UpdateParameters.Add('LargeIcon', $LargeIconValue) + } + + Update-MgBetaDeviceAppManagementMobileApp -MobileAppId $currentInstance.Id @UpdateParameters + Write-Host "Updated MacOS App: $DisplayName." + + #Assignments + $assignmentsHash = ConvertTo-IntuneMobileAppAssignment -IncludeDeviceFilter:$true -Assignments $Assignments + Update-MgBetaDeviceAppManagementMobileAppAssignment -MobileAppId $currentInstance.id ` + -Target $assignmentsHash ` + -Repository 'deviceAppManagement/mobileAppAssignments' + } + # REMOVE + elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') + { + Write-Host "Remove MacOS app: $DisplayName" + Remove-MgBetaDeviceAppManagementMobileApp -MobileAppId $currentInstance.Id -Confirm:$false + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + #region resource parameters + + [Parameter()] + [System.String] + $Id, + + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.Boolean] + $IgnoreVersionDetection, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds, + + [Parameter()] + [System.String] + $BundleId, + + [Parameter()] + [System.String] + $BuildNumber, + + [Parameter()] + [System.String] + $VersionNumber, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $ChildApps, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + #endregion + + [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()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [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 + + Write-Verbose -Message "Testing configuration of Intune Mobile MacOS App: {$DisplayName}" + + $CurrentValues = Get-TargetResource @PSBoundParameters + if (-not (Test-M365DSCAuthenticationParameter -BoundParameters $CurrentValues)) + { + Write-Verbose "An error occured in Get-TargetResource, the app {$displayName} will not be processed" + throw "An error occured in Get-TargetResource, the app {$displayName} will not be processed. Refer to the event viewer logs for more information." + } + $ValuesToCheck = ([Hashtable]$PSBoundParameters).clone() + $ValuesToCheck = Remove-M365DSCAuthenticationParameter -BoundParameters $ValuesToCheck + $ValuesToCheck.Remove('Id') | Out-Null + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" + + if ($CurrentValues.Ensure -ne $Ensure) + { + Write-Verbose -Message "Test-TargetResource returned $false" + return $false + } + $testResult = $true + + #Compare Cim instances + foreach ($key in $PSBoundParameters.Keys) + { + $source = $PSBoundParameters.$key + $target = $CurrentValues.$key + if ($source.getType().Name -like '*CimInstance*') + { + $testResult = Compare-M365DSCComplexObject ` + -Source ($source) ` + -Target ($target) + + if (-Not $testResult) + { + $testResult = $false + break + } + + $ValuesToCheck.Remove($key) | Out-Null + } + } + + if ($testResult) + { + $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.String] + $CertificateThumbprint, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -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:getInstances = Get-MgBetaDeviceAppManagementMobileApp ` + -Filter "isof('microsoft.graph.macOSLobApp')" ` + -ExpandProperty "categories,assignments" ` + -ErrorAction Stop | Where-Object ` + -FilterScript { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.macOSLobApp' } + + $i = 1 + $dscContent = '' + if ($Script:getInstances.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + + foreach ($config in $Script:getInstances) + { + if ($null -ne $Global:M365DSCExportResourceInstancesCount) + { + $Global:M365DSCExportResourceInstancesCount++ + } + + $displayedKey = $config.Id + Write-Host " |---[$i/$($Script:getInstances.Count)] $displayedKey" -NoNewline + + $params = @{ + Id = $config.Id + Description = $config.Description + Developer = $config.Developer + DisplayName = $config.DisplayName + InformationUrl = $config.InformationUrl + IsFeatured = $config.IsFeatured + Notes = $config.Notes + Owner = $config.Owner + PrivacyInformationUrl = $config.PrivacyInformationUrl + Publisher = $config.Publisher + PublishingState = $config.PublishingState.ToString() + RoleScopeTagIds = $config.RoleScopeTagIds + BundleId = $config.BundleId + BuildNumber = $config.BuildNumber + VersionNumber = $config.VersionNumber + IgnoreVersionDetection = $config.AdditionalProperties.ignoreVersionDetection + + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + ApplicationSecret = $ApplicationSecret + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + $Results = Get-TargetResource @params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + + if (-not (Test-M365DSCAuthenticationParameter -BoundParameters $Results)) + { + Write-Verbose "An error occured in Get-TargetResource, the app {$($params.displayName)} will not be processed." + throw "An error occured in Get-TargetResource, the app {$($params.displayName)} will not be processed. Refer to the event viewer logs for more information." + } + + #region complex types + + #Categories + if($null -ne $Results.Categories) + { + $Results.Categories = Get-M365DSCIntuneAppCategoriesAsString -Categories $Results.Categories + } + else { + $Results.Remove('Categories') | Out-Null + } + + #ChildApps + if($null -ne $Results.childApps) + { + $Results.childApps = Get-M365DSCIntuneAppChildAppsAsString -ChildApps $Results.childApps + } + else { + $Results.Remove('childApps') | Out-Null + } + + #Assignments + if ($null -ne $Results.Assignments) + { + $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString -ComplexObject ([Array]$Results.Assignments) -CIMInstanceName DeviceManagementMobileAppAssignment + + if ($complexTypeStringResult) + { + $Results.Assignments = $complexTypeStringResult + } + else + { + $Results.Remove('Assignments') | Out-Null + } + } + + #LargeIcon + if($null -ne $Results.LargeIcon) + { + $Results.LargeIcon = Get-M365DSCIntuneAppLargeIconAsString -LargeIcon $Results.LargeIcon + } + else + { + $Results.Remove('LargeIcon') | Out-Null + } + + #endregion complex types + + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + + #region complex types + + #Categories + if ($null -ne $Results.Categories) + { + $isCIMArray = $false + if ($Results.Categories.getType().Fullname -like '*[[\]]') + { + $isCIMArray = $true + } + + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Categories' -IsCIMArray:$isCIMArray + } + + #ChildApps + if ($null -ne $Results.childApps) + { + $isCIMArray = $false + if ($Results.childApps.getType().Fullname -like '*[[\]]') + { + $isCIMArray = $true + } + + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'ChildApps' -IsCIMArray:$isCIMArray + } + + #Assignments + if ($null -ne $Results.Assignments) + { + $isCIMArray = $false + if ($Results.Assignments.getType().Fullname -like '*[[\]]') + { + $isCIMArray = $true + } + + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Assignments' -IsCIMArray:$isCIMArray + } + + #LargeIcon + if ($null -ne $Results.LargeIcon) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'LargeIcon' -IsCIMArray:$false + } + + #endregion complex types + + $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 '' + } +} + +#region Helper functions + +function Get-M365DSCIntuneAppCategoriesAsString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true)] + [System.Object[]] + $Categories + ) + + $StringContent = '@(' + $space = ' ' + $indent = ' ' + + $i = 1 + foreach ($category in $Categories) + { + if ($Categories.Count -gt 1) + { + $StringContent += "`r`n" + $StringContent += "$space" + } + + #Only export the displayName, not Id + $StringContent += "MSFT_DeviceManagementMobileAppCategory { `r`n" + $StringContent += "$($space)$($indent)displayName = '" + $category.displayName + "'`r`n" + $StringContent += "$space}" + + $i++ + } + + $StringContent += ')' + + return $StringContent +} + +function Get-M365DSCIntuneAppChildAppsAsString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true)] + [System.Object[]] + $ChildApps + ) + + $StringContent = '@(' + $space = ' ' + $indent = ' ' + + $i = 1 + foreach ($childApp in $ChildApps) + { + if ($ChildApps.Count -gt 1) + { + $StringContent += "`r`n" + $StringContent += "$space" + } + + $StringContent += "MSFT_DeviceManagementMobileAppChildApp { `r`n" + $StringContent += "$($space)$($indent)bundleId = '" + $childApp.bundleId + "'`r`n" + $StringContent += "$($space)$($indent)buildNumber = '" + $childApp.buildNumber + "'`r`n" + $StringContent += "$($space)$($indent)versionNumber = '" + $childApp.versionNumber + "'`r`n" + $StringContent += "$space}" + + $i++ + } + + $StringContent += ')' + + return $StringContent +} + +function Get-M365DSCIntuneMobileMocOSLobAppAdditionalProperties +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = 'true')] + [System.Collections.Hashtable] + $Properties + ) + + $additionalProperties = @( + 'IgnoreVersionDetection' + 'ChildApps' + ) + + $results = @{'@odata.type' = '#microsoft.graph.macOSLobApp' } + $cloneProperties = $Properties.clone() + foreach ($property in $cloneProperties.Keys) + { + if ($property -in $additionalProperties) + { + $propertyName = $property[0].ToString().ToLower() + $property.Substring(1, $property.Length - 1) + if ($properties.$property -and $properties.$property.getType().FullName -like '*CIMInstance*') + { + if ($properties.$property.getType().FullName -like '*[[\]]') + { + $array = @() + foreach ($item in $properties.$property) + { + $array += Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $item + + } + $propertyValue = $array + } + else + { + $propertyValue = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $properties.$property + } + + } + else + { + $propertyValue = $properties.$property + } + + $results.Add($propertyName, $propertyValue) + } + } + + if ($results.Count -eq 1) + { + return $null + } + return $results +} + +function Get-M365DSCIntuneAppLargeIconAsString #Get and Export +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true)] + [System.Object] + $LargeIcon + ) + + $space = ' ' + $indent = ' ' + + if ($null -ne $LargeIcon.Value) + { + $StringContent += "`r`n" + $StringContent += "$space" + + $base64String = [System.Convert]::ToBase64String($LargeIcon.Value) # This exports the base64 string (blob) of the byte array, same as we see in Graph API response + + $StringContent += "MSFT_DeviceManagementMimeContent { `r`n" + $StringContent += "$($space)$($indent)type = '" + $LargeIcon.Type + "'`r`n" + $StringContent += "$($space)$($indent)value = '" + $base64String + "'`r`n" + $StringContent += "$space}" + } + + return $StringContent + } + +function ConvertTo-M365DSCIntuneAppLargeIcon #set +{ + [OutputType([System.Object])] + param( + [Parameter(Mandatory = $true)] + [System.Object] + $LargeIcon + ) + + $result = @{ + type = $LargeIcon.Type + value = $iconValue + } + + return $result +} + +#endregion Helper functions + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.schema.mof new file mode 100644 index 0000000000..88248b6460 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/MSFT_IntuneMobileAppsMacOSLobApp.schema.mof @@ -0,0 +1,77 @@ +[ClassVersion("1.0.0"), FriendlyName("IntuneMobileAppsMacOSLobApp")] +class MSFT_IntuneMobileAppsMacOSLobApp : OMI_BaseResource +{ + [Key, Description("The admin provided or imported title of the app. Inherited from mobileApp.")] String DisplayName; + [Write, Description("The unique identifier for an entity. Read-only. Inherited from mobileApp object.")] String Id; + + [Write, Description("The description of the app. Inherited from mobileApp.")] String Description; + [Write, Description("The dewveloper of the app. Inherited from mobileApp.")] String Developer; + [Write, Description("The InformationUrl of the app. Inherited from mobileApp.")] String InformationUrl; + [Write, Description("The value indicating whether the app is marked as featured by the admin. Inherited from mobileApp.")] Boolean IsFeatured; + [Write, Description("Notes for the app. Inherited from mobileApp.")] String Notes; + [Write, Description("The owner of the app. Inherited from mobileApp.")] String Owner; + [Write, Description("The privacy statement Url. Inherited from mobileApp.")] String PrivacyInformationUrl; + [Write, Description("The publisher of the app. Inherited from mobileApp.")] String Publisher; + [Write, Description("The publishing state for the app. The app cannot be assigned unless the app is published. Inherited from mobileApp."), ValueMap{"notPublished", "processing","published"}, Values{"notPublished", "processing", "published"}] String PublishingState; + [Write, Description("The bundleId of the app.")] String BundleId; + [Write, Description("The build number of the app.")] String BuildNumber; + [Write, Description("The version number of the app.")] String VersionNumber; + [Write, Description("List of Scope Tag IDs for mobile app.")] String RoleScopeTagIds[]; + [Write, Description("Wether to ignore the version of the app or not.")] Boolean IgnoreVersionDetection; + [Write, Description("The icon for this app."), EmbeddedInstance("MSFT_DeviceManagementMimeContent")] String LargeIcon; + [Write, Description("The list of categories for this app."), EmbeddedInstance("MSFT_DeviceManagementMobileAppCategory")] String Categories[]; + [Write, Description("The list of assignments for this app."), EmbeddedInstance("MSFT_DeviceManagementMobileAppAssignment")] String Assignments[]; + [Write, Description("The list of child apps for this app package."), EmbeddedInstance("MSFT_DeviceManagementMobileAppChildApp")] String ChildApps[]; + + [Write, Description("Present ensures the policy exists, absent ensures it is removed."), ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] String Ensure; + [Write, Description("Credentials of the 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("Secret of the Azure Active Directory tenant used for authentication."), EmbeddedInstance("MSFT_Credential")] String ApplicationSecret; + [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[]; +}; + +class MSFT_DeviceManagementMimeContent +{ + [Write, Description("Indicates the type of content mime.")] String type; + [Write, Description("The byte array that contains the actual content.")] String value[]; +}; + +class MSFT_DeviceManagementMobileAppCategory +{ + [Key, Description("The name of the app category.")] String displayName; + [Write, Description("The unique identifier for an entity. Read-only.")] String id; +}; + +class MSFT_DeviceManagementMobileAppChildApp +{ + [Write, Description("The bundleId of the app.")] String bundleId; + [Write, Description("The build number of the app.")] String buildNumber; + [Write, Description("The version number of the app.")] String versionNumber; +}; + +class MSFT_DeviceManagementMobileAppAssignment +{ + [Write, Description("The type of the target assignment."), + ValueMap{"#microsoft.graph.groupAssignmentTarget","#microsoft.graph.allLicensedUsersAssignmentTarget","#microsoft.graph.allDevicesAssignmentTarget","#microsoft.graph.exclusionGroupAssignmentTarget", "#microsoft.graph.mobileAppAssignment"}, + Values{"#microsoft.graph.groupAssignmentTarget","#microsoft.graph.allLicensedUsersAssignmentTarget","#microsoft.graph.allDevicesAssignmentTarget","#microsoft.graph.exclusionGroupAssignmentTarget", "#microsoft.graph.mobileAppAssignment"}] + String dataType; + + [Write, Description("The Id of the filter for the target assignment.")] String deviceAndAppManagementAssignmentFilterId; + [Write, Description("The type of filter of the target assignment i.e. Exclude or Include. Possible values are: none, include, exclude."), + ValueMap{"none", "include", "exclude"}, + Values{"none", "include", "exclude"}] + String deviceAndAppManagementAssignmentFilterType; + + [Write, Description("The group Id that is the target of the assignment.")] String groupId; + [Write, Description("The group Display Name that is the target of the assignment.")] String groupDisplayName; + + [Write, Description("Possible values for the install intent chosen by the admin."), + ValueMap{"available", "required", "uninstall", "availableWithoutEnrollment"}, + Values{"available", "required", "uninstall", "availableWithoutEnrollment"}] + String intent; + + [Write, Description("The source of this assignment.")] String source; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/readme.md new file mode 100644 index 0000000000..821600b758 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/readme.md @@ -0,0 +1,6 @@ + +# IntuneMobileAppsMacOSLobApp + +## Description + +This resource configures an Intune mobile app of MacOSLobApp type for MacOS devices. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/settings.json new file mode 100644 index 0000000000..3e70ad560b --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsMacOSLobApp/settings.json @@ -0,0 +1,32 @@ +{ + "resourceName": "IntuneMobileAppsMacOSLobApp", + "description": "This resource configures an Intune mobile app.", + "permissions": { + "graph": { + "delegated": { + "read": [ + { + "name": "DeviceManagementApps.Read.All" + } + ], + "update": [ + { + "name": "DeviceManagementApps.ReadWrite.All" + } + ] + }, + "application": { + "read": [ + { + "name": "DeviceManagementApps.Read.All" + } + ], + "update": [ + { + "name": "DeviceManagementApps.ReadWrite.All" + } + ] + } + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/1-Create.ps1 new file mode 100644 index 0000000000..1998735068 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/1-Create.ps1 @@ -0,0 +1,60 @@ +<# +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 + { + IntuneMobileAppsMacOSLobApp "IntuneMobileAppsMacOSLobApp-TeamsForBusinessInstaller" + { + Id = "8d027f94-0682-431e-97c1-827d1879fa79"; + Description = "TeamsForBusinessInstaller"; + Developer = "Contoso"; + DisplayName = "TeamsForBusinessInstaller"; + Ensure = "Present"; + InformationUrl = ""; + IsFeatured = $False; + Notes = ""; + Owner = ""; + PrivacyInformationUrl = ""; + Publisher = "Contoso"; + PublishingState = "published"; + Assignments = @( + MSFT_DeviceManagementMobileAppAssignment{ + groupDisplayName = 'All devices' + source = 'direct' + deviceAndAppManagementAssignmentFilterType = 'none' + dataType = '#microsoft.graph.allDevicesAssignmentTarget' + intent = 'required' + MSFT_DeviceManagementMobileAppAssignment{ + deviceAndAppManagementAssignmentFilterType = 'none' + source = 'direct' + dataType = '#microsoft.graph.groupAssignmentTarget' + groupId = '57b5e81c-85bb-4644-a4fd-33b03e451c89' + intent = 'required' + } + }); + Categories = @(MSFT_DeviceManagementMobileAppCategory { + id = '1bff2652-03ec-4a48-941c-152e93736515' + displayName = 'Kajal 3' + }); + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/2-Update.ps1 new file mode 100644 index 0000000000..dccf286c8b --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/2-Update.ps1 @@ -0,0 +1,60 @@ +<# +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 + { + IntuneMobileAppsMacOSLobApp "IntuneMobileAppsMacOSLobApp-TeamsForBusinessInstaller" + { + Id = "8d027f94-0682-431e-97c1-827d1879fa79"; + Description = "TeamsForBusinessInstaller"; + Developer = "Contoso drift"; #drift + DisplayName = "TeamsForBusinessInstaller"; + Ensure = "Present"; + InformationUrl = ""; + IsFeatured = $False; + Notes = ""; + Owner = ""; + PrivacyInformationUrl = ""; + Publisher = "Contoso"; + PublishingState = "published"; + Assignments = @( + MSFT_DeviceManagementMobileAppAssignment{ + groupDisplayName = 'All devices' + source = 'direct' + deviceAndAppManagementAssignmentFilterType = 'none' + dataType = '#microsoft.graph.allDevicesAssignmentTarget' + intent = 'required' + MSFT_DeviceManagementMobileAppAssignment{ + deviceAndAppManagementAssignmentFilterType = 'none' + source = 'direct' + dataType = '#microsoft.graph.groupAssignmentTarget' + groupId = '57b5e81c-85bb-4644-a4fd-33b03e451c89' + intent = 'required' + } + }); + Categories = @(MSFT_DeviceManagementMobileAppCategory { + id = '1bff2652-03ec-4a48-941c-152e93736515' + displayName = 'Kajal 3' + }); + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/3-Remove.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/3-Remove.ps1 new file mode 100644 index 0000000000..33cc9276b4 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsMacOSLobApp/3-Remove.ps1 @@ -0,0 +1,41 @@ +<# +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 + { + IntuneMobileAppsMacOSLobApp "IntuneMobileAppsMacOSLobApp-TeamsForBusinessInstaller" + { + Id = "8d027f94-0682-431e-97c1-827d1879fa79"; + Description = "TeamsForBusinessInstaller"; + Developer = "Contoso"; + DisplayName = "TeamsForBusinessInstaller"; + Ensure = "Absent"; + InformationUrl = ""; + IsFeatured = $False; + Notes = ""; + Owner = ""; + PrivacyInformationUrl = ""; + Publisher = "Contoso"; + PublishingState = "published"; + } + } +} diff --git a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 index 8d1e1f69d7..30baaf6254 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 @@ -285,6 +285,7 @@ function Get-M365DSCDRGComplexTypeToString { $indent += ' ' } + #If ComplexObject is an Array if ($ComplexObject.GetType().FullName -like '*[[\]]') { @@ -305,7 +306,7 @@ function Get-M365DSCDRGComplexTypeToString $currentProperty += Get-M365DSCDRGComplexTypeToString -IsArray @splat } - # PowerShell returns all non-captured stream output, not just the argument of the return statement. + #PowerShell returns all non-captured stream output, not just the argument of the return statement. #An empty array is mangled into $null in the process. #However, an array can be preserved on return by prepending it with the array construction operator (,) return , $currentProperty @@ -616,6 +617,7 @@ function Compare-M365DSCComplexObject } if ($Source[0].CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or + $Source[0].CimClass.CimClassName -eq 'MSFT_DeviceManagementMobileAppAssignment' -or ($Source[0].CimClass.CimClassName -like 'MSFT_Intune*Assignments' -and $Source[0].CimClass.CimClassName -ne 'MSFT_IntuneDeviceRemediationPolicyAssignments')) { @@ -733,7 +735,9 @@ function Compare-M365DSCComplexObject { if ($Source.$key.GetType().FullName -like '*CimInstance' -and ( $Source.$key.CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or - $Source.$key.CimClass.CimClassName -like 'MSFT_Intune*Assignments')) + $Source.$key.CimClass.CimClassName -like 'MSFT_DeviceManagementMobileAppAssignment' -or + $Source.$key.CimClass.CimClassName -like 'MSFT_Intune*Assignments' + )) { $compareResult = Compare-M365DSCIntunePolicyAssignment ` -Source @($Source.$key) ` @@ -933,6 +937,7 @@ function ConvertTo-IntunePolicyAssignment [Parameter(Mandatory = $true)] [AllowNull()] $Assignments, + [Parameter()] [System.Boolean] $IncludeDeviceFilter = $true @@ -1007,6 +1012,182 @@ function ConvertTo-IntunePolicyAssignment return ,$assignmentResult } +function ConvertFrom-IntuneMobileAppAssignment +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable[]])] + param ( + [Parameter(Mandatory = $true)] + [Array] + $Assignments, + [Parameter()] + [System.Boolean] + $IncludeDeviceFilter = $true + ) + + $assignmentResult = @() + foreach ($assignment in $Assignments) + { + $hashAssignment = @{} + if ($null -ne $assignment.Target.'@odata.type') + { + $dataType = $assignment.Target.'@odata.type' + } + else + { + $dataType = $assignment.Target.AdditionalProperties.'@odata.type' + } + + if ($null -ne $assignment.Target.groupId) + { + $groupId = $assignment.Target.groupId + } + else + { + $groupId = $assignment.Target.AdditionalProperties.groupId + } + + $hashAssignment.Add('dataType', $dataType) + if (-not [string]::IsNullOrEmpty($groupId)) + { + $hashAssignment.Add('groupId', $groupId) + + $group = Get-MgGroup -GroupId ($groupId) -ErrorAction SilentlyContinue + if ($null -ne $group) + { + $groupDisplayName = $group.DisplayName + } + } + + if ($dataType -eq '#microsoft.graph.allLicensedUsersAssignmentTarget') + { + $groupDisplayName = 'All users' + } + if ($dataType -eq '#microsoft.graph.allDevicesAssignmentTarget') + { + $groupDisplayName = 'All devices' + } + if ($null -ne $groupDisplayName) + { + $hashAssignment.Add('groupDisplayName', $groupDisplayName) + } + + $hashAssignment.Add('intent', $assignment.intent.ToString()) + $hashAssignment.Add('source', $assignment.source.ToString()) + + # $concatenatedSettings = $assignment.settings.ToString() -join ',' + # $hashAssignment.Add('settings', $concatenatedSettings) + # $hashSettings = @{} + # foreach ($setting in $assignment.Settings) + # { + # $hashSettings.Add('datatype', $setting.dataType) + # $hashSettings.Add('uninstallOnDeviceRemoval', $setting.uninstallOnDeviceRemoval) + # } + # $hashAssignment.Add('settings', $hashSettings) + + if ($IncludeDeviceFilter) + { + if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterType) + { + $hashAssignment.Add('deviceAndAppManagementAssignmentFilterType', $assignment.Target.DeviceAndAppManagementAssignmentFilterType.ToString()) + } + if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterId) + { + $hashAssignment.Add('deviceAndAppManagementAssignmentFilterId', $assignment.Target.DeviceAndAppManagementAssignmentFilterId) + } + } + + $assignmentResult += $hashAssignment + } + + return ,$assignmentResult +} + +function ConvertTo-IntuneMobileAppAssignment +{ + [CmdletBinding()] + [OutputType([Hashtable[]])] + param ( + [Parameter(Mandatory = $true)] + [AllowNull()] + $Assignments, + + [Parameter()] + [System.Boolean] + $IncludeDeviceFilter = $true + ) + + if ($null -eq $Assignments) + { + return ,@() + } + + $assignmentResult = @() + foreach ($assignment in $Assignments) + { + $target = @{"@odata.type" = $assignment.dataType} + if ($IncludeDeviceFilter) + { + if ($null -ne $assignment.DeviceAndAppManagementAssignmentFilterType) + { + $target.Add('deviceAndAppManagementAssignmentFilterType', $assignment.DeviceAndAppManagementAssignmentFilterType) + $target.Add('deviceAndAppManagementAssignmentFilterId', $assignment.DeviceAndAppManagementAssignmentFilterId) + + $target.GetEnumerator() | ForEach-Object { + Write-Host "target key:value: $($_.Key): $($_.Value)" } + } + } + + $assignmentResult += $assignment.intent; + $assignmentResult += $assignment.source; + + if ($assignment.dataType -like '*groupAssignmentTarget') + { + $group = Get-MgGroup -GroupId ($assignment.groupId) -ErrorAction SilentlyContinue + if ($null -eq $group) + { + if ($assignment.groupDisplayName) + { + $group = Get-MgGroup -Filter "DisplayName eq '$($assignment.groupDisplayName)'" -ErrorAction SilentlyContinue + if ($null -eq $group) + { + $message = "Skipping assignment for the group with DisplayName {$($assignment.groupDisplayName)} as it could not be found in the directory.`r`n" + $message += "Please update your DSC resource extract with the correct groupId or groupDisplayName." + Write-Verbose -Message $message + $target = $null + } + if ($group -and $group.Count -gt 1) + { + $message = "Skipping assignment for the group with DisplayName {$($assignment.groupDisplayName)} as it is not unique in the directory.`r`n" + $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." + Write-Verbose -Message $message + $group = $null + $target = $null + } + } + else + { + $message = "Skipping assignment for the group with Id {$($assignment.groupId)} as it could not be found in the directory.`r`n" + $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." + Write-Verbose -Message $message + $target = $null + } + } + else { + #Skipping assignment if group not found from either groupId or groupDisplayName + $target.Add('groupId', $group.Id) + } + } + + if ($target) + { + $assignmentResult += @{target = $target} + } + } + + return ,$assignmentResult +} + function Compare-M365DSCIntunePolicyAssignment { [CmdletBinding()] diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsMacOSLobApp.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsMacOSLobApp.Tests.ps1 new file mode 100644 index 0000000000..1f0c71c7a5 --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsMacOSLobApp.Tests.ps1 @@ -0,0 +1,274 @@ +[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 Get-MgBetaDeviceAppManagementMobileApp -MockWith { + } + Mock -CommandName New-MgBetaDeviceAppManagementMobileApp -MockWith { + } + Mock -CommandName Update-MgBetaDeviceAppManagementMobileApp -MockWith { + } + Mock -CommandName Remove-MgBetaDeviceAppManagementMobileApp -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 "1. The instance should exist but it DOES NOT" -Fixture { + BeforeAll { + $testParams = @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + + Ensure = 'Present' + Credential = $Credential + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileApp -MockWith { + return $null + } + } + + It '1.1 Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' + } + It '1.2 Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + It '1.3 Should create a new instance from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName New-MgBetaDeviceAppManagementMobileApp -Exactly 1 + } + } + + Context -Name "2. The instance exists but it SHOULD NOT" -Fixture { + BeforeAll { + $testParams = @{ + Id = "ad027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + IgnoreVersionDetection = $True + + Ensure = 'Absent' + Credential = $Credential + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileApp -MockWith { + return @{ + Id = "ad027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + IgnoreVersionDetection = $True + + Ensure = 'Present' + } + } + } + + It '2.1 Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + It '2.2 Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + It '2.3 Should remove the instance from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Remove-MgBetaDeviceAppManagementMobileApp -Exactly 1 + } + } + + Context -Name "3. The instance exists and values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + + Ensure = 'Present' + Credential = $Credential; + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileApp -MockWith { + return @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + } + } + } + + It '3.0 Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "4. The instance exists and values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + + Ensure = 'Present' + Credential = $Credential; + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileApp -MockWith { + return @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller drift" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + } + } + } + + It '4.1 Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + It '4.2 Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + It '4.3 Should call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Update-MgBetaDeviceAppManagementMobileApp -Exactly 1 + } + } + + Context -Name '5. ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential; + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileApp -MockWith { + return @{ + Id = "8d027f94-0682-431e-97c1-827d1879fa79" + Description = "TeamsForBusinessInstaller" + Developer = "Contoso" + DisplayName = "TeamsForBusinessInstaller drift" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + } + } + } + + It '5.0 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/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index fa8243de08..2ab603a280 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -19347,6 +19347,209 @@ function Get-MgBetaDeviceManagementGroupPolicyConfigurationAssignment $HttpPipelineAppend ) } + +function New-MgBetaDeviceAppManagementMobileApp { + [CmdletBinding()] + param ( + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $ChildApps, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds + ) +} + +function Get-MgBetaDeviceAppManagementMobileApp { + [CmdletBinding()] + param ( + [Parameter()] + [System.String] + $MobileAppId, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.Boolean] + $IgnoreVersionDetection, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds + ) +} +function Update-MgBetaDeviceAppManagementMobileApp { + [CmdletBinding()] + param ( + [Parameter()] + [System.String] + $MobileAppId, + + [Parameter()] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Description, + + [Parameter()] + [System.String] + $Developer, + + [Parameter()] + [System.String] + $InformationUrl, + + [Parameter()] + [System.Boolean] + $IsFeatured, + + [Parameter()] + [System.String] + $Notes, + + [Parameter()] + [System.String] + $Owner, + + [Parameter()] + [System.String] + $PrivacyInformationUrl, + + [Parameter()] + [System.String] + $Publisher, + + [Parameter()] + [System.String] + [ValidateSet('notPublished', 'processing','published')] + $PublishingState, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $ChildApps, + + [Parameter()] + [System.String[]] + $RoleScopeTagIds + ) +} + +function Remove-MgBetaDeviceAppManagementMobileApp { + [CmdletBinding()] + param ( + [Parameter()] + [System.String] + $MobileAppId, + + [Parameter()] + [System.Boolean] + $Confirm + ) +} + function Get-MgBetaDeviceManagementGroupPolicyConfigurationDefinitionValue { [CmdletBinding()] @@ -90606,7 +90809,7 @@ function Update-MgBetaDeviceAppManagementPolicySetAssignment #endregion #region MgBetaDeviceAppManagementMobileApp -function Get-MgBetaDeviceAppManagementMobileApp +function Get-MgBetaDeviceAppManagementMobileApp # TODOK { [CmdletBinding()] param @@ -90693,7 +90896,7 @@ function Get-MgBetaDeviceAppManagementMobileApp ) } -function New-MgBetaDeviceAppManagementMobileApp +function New-MgBetaDeviceAppManagementMobileApp # TODOK { [CmdletBinding()] param @@ -90832,7 +91035,7 @@ function New-MgBetaDeviceAppManagementMobileApp ) } -function Remove-MgBetaDeviceAppManagementMobileApp +function Remove-MgBetaDeviceAppManagementMobileApp # TODOK { [CmdletBinding()] param @@ -90879,74 +91082,15 @@ function Remove-MgBetaDeviceAppManagementMobileApp [Parameter()] [System.Management.Automation.SwitchParameter] - $ProxyUseDefaultCredentials - ) -} - -function Set-MgBetaDeviceAppManagementMobileApp -{ - [CmdletBinding()] - param - ( - [Parameter()] - [System.String] - $MobileAppId, - - [Parameter()] - [PSObject] - $InputObject, - - [Parameter()] - [PSObject] - $BodyParameter, - - [Parameter()] - [System.String] - $ResponseHeadersVariable, - - [Parameter()] - [System.Collections.Hashtable] - $AdditionalProperties, - - [Parameter()] - [PSObject[]] - $MobileAppAssignments, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Break, - - [Parameter()] - [System.Collections.IDictionary] - $Headers, - - [Parameter()] - [PSObject[]] - $HttpPipelineAppend, - - [Parameter()] - [PSObject[]] - $HttpPipelinePrepend, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $PassThru, - - [Parameter()] - [System.Uri] - $Proxy, - - [Parameter()] - [System.Management.Automation.PSCredential] - $ProxyCredential, + $ProxyUseDefaultCredentials, [Parameter()] - [System.Management.Automation.SwitchParameter] - $ProxyUseDefaultCredentials + [System.Boolean] + $Confirm ) } -function Update-MgBetaDeviceAppManagementMobileApp +function Update-MgBetaDeviceAppManagementMobileApp # TODOK { [CmdletBinding()] param