diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.mof new file mode 100644 index 0000000000..a02fcc50dd --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.mof @@ -0,0 +1,81 @@ +[ClassVersion("1.0.0"), FriendlyName("IntuneMobileAppsWebApp")] +class MSFT_IntuneMobileAppsWebApp : 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("List of Scope Tag IDs for mobile app.")] String RoleScopeTagIds[]; + [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 icon for this app."), EmbeddedInstance("MSFT_DeviceManagementMimeContent")] String LargeIcon; + [Write, Description("The URL of the web app.")] String AppUrl; + [Write, Description("The number of apps this app has superseded. Read-only.")] UInt32 SupersededAppCount; + [Write, Description("The number of apps that supersede this app. Read-only.")] UInt32 SupersedingAppCount; + [Write, Description("The number of apps that depend on this app. Read-only.")] UInt32 DependentAppCount; + [Write, Description("Indicates whether the app is assigned to any groups.")] Boolean IsAssigned; + [Write, Description("The current upload state of the app. Read-only.")] UInt32 UploadState; + [Write, Description("The date and time the app was created. Read-only.")] DateTime CreatedDateTime; + [Write, Description("The date and time the app was last modified. Read-only.")] DateTime LastModifiedDateTime; + [Write, Description("Whether or not to use a managed browser for this app. Applicable only for Android and iOS.")] Boolean UseManagedBrowser; + + [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_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.psm1 new file mode 100644 index 0000000000..18ab806e57 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/MSFT_IntuneMobileAppsWebApp.psm1 @@ -0,0 +1,1089 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + #region Intune resource parameters + + [Parameter(Mandatory = $true)] + [System.String] + $DisplayName, + + [Parameter()] + [System.String] + $Id, + + [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()] + [System.String[]] + $RoleScopeTagIds, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + [Parameter()] + [System.String] + $AppUrl, + + [Parameter()] + [System.Int32] + $SupersededAppCount, + + [Parameter()] + [System.Int32] + $SupersedingAppCount, + + [Parameter()] + [System.Int32] + $DependentAppCount, + + [Parameter()] + [System.Boolean] + $IsAssigned, + + [Parameter()] + [System.Int32] + $UploadState, + + [Parameter()] + [System.DateTime] + $CreatedDateTime, + + [Parameter()] + [System.DateTime] + $LastModifiedDateTime, + + [Parameter()] + [System.Boolean] + $UseManagedBrowser, + + #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.webApp') and displayName eq '$DisplayName')" ` + -ExpandProperty "categories,assignments" ` + -ErrorAction SilentlyContinue | Where-Object ` + -FilterScript { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.webApp' } + + 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.webApp' } + } + + 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 + AppUrl = $instance.AppUrl + SupersededAppCount = $instance.SupersededAppCount + SupersedingAppCount = $instance.SupersedingAppCount + DependentAppCount = $instance.DependentAppCount + IsAssigned = $instance.IsAssigned + UploadState = $instance.UploadState + CreatedDateTime = $instance.CreatedDateTime + LastModifiedDateTime = $instance.LastModifiedDateTime + UseManagedBrowser = $instance.UseManagedBrowser + + 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', "") + } + + #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.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()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + [Parameter()] + [System.String] + $AppUrl, + + [Parameter()] + [System.Boolean] + $IsAssigned, + + [Parameter()] + [System.Boolean] + $useManagedBrowser, + + #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 web app: $DisplayName" + + $CreateParameters = ([Hashtable]$PSBoundParameters).clone() + $CreateParameters = Rename-M365DSCCimInstanceParameter -Properties $CreateParameters + + $AdditionalProperties = Get-M365DSCIntuneMobileWebAppAdditionalProperties -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 web app: $DisplayName" + + $PSBoundParameters.Remove('Assignments') | Out-Null + $UpdateParameters = ([Hashtable]$PSBoundParameters).clone() + $UpdateParameters = Rename-M365DSCCimInstanceParameter -Properties $UpdateParameters + + $AdditionalProperties = Get-M365DSCIntuneMobileWebAppAdditionalProperties -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 web 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 web 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.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()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Categories, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]] + $Assignments, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $LargeIcon, + + [Parameter()] + [System.String] + $AppUrl, + + [Parameter()] + [System.Boolean] + $IsAssigned, + + [Parameter()] + [System.Boolean] + $useManagedBrowser + + #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 web 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.webApp')" ` + -ExpandProperty "categories,assignments" ` + -ErrorAction Stop | Where-Object ` + -FilterScript { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.webApp' } + + $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 + DisplayName = $config.DisplayName + 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-M365DSCIntuneMobileWebAppAdditionalProperties +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = 'true')] + [System.Collections.Hashtable] + $Properties + ) + + $additionalProperties = @( + 'AppUrl' + 'SupersededAppCount' + 'SupersedingAppCount' + 'DependentAppCount' + 'IsAssigned' + 'UploadState' + 'CreatedDateTime' + 'LastModifiedDateTime' + 'UseManagedBrowser' + ) + + $results = @{'@odata.type' = '#microsoft.graph.webApp' } + $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_IntuneMobileAppsWebApp/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/readme.md new file mode 100644 index 0000000000..88b3fa5981 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/readme.md @@ -0,0 +1,6 @@ + +# IntuneMobileAppsWebApp + +## Description + +This resource configures an Intune mobile app of WebApp type. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/settings.json new file mode 100644 index 0000000000..6e76e91c3b --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneMobileAppsWebApp/settings.json @@ -0,0 +1,32 @@ +{ + "resourceName": "IntuneMobileAppsWebApp", + "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/IntuneMobileAppsWebApp/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/1-Create.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/1-Create.ps1 @@ -0,0 +1,26 @@ +<# +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 + { + + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/2-Update.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/2-Update.ps1 @@ -0,0 +1,26 @@ +<# +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 + { + + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/3-Remove.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/3-Remove.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneMobileAppsWebApp/3-Remove.ps1 @@ -0,0 +1,26 @@ +<# +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 + { + + } +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsWebApp.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsWebApp.Tests.ps1 new file mode 100644 index 0000000000..a33a1bf65a --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneMobileAppsWebApp.Tests.ps1 @@ -0,0 +1,193 @@ +[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 -CommandName Update-MgBetaDeviceAppManagementMobileAppAssignment -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 = "Sample Web App" + Developer = "Contoso" + DisplayName = "SampleWebApp" + 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 = "Sample Web App" + Developer = "Contoso" + DisplayName = "SampleWebApp" + 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 = "Sample Web App" + Developer = "Contoso" + DisplayName = "SampleWebApp" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + IgnoreVersionDetection = $True + AdditionalProperties = @{ + '@odata.type' = '#microsoft.graph.webApp' + } + Ensure = 'Present' + } + } + + Mock -CommandName Get-MgBetaDeviceAppManagementMobileAppAssignment -MockWith{ + return $null + } + } + + 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 '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 = "Sample Web App" + Developer = "Contoso" + DisplayName = "SampleWebApp" + InformationUrl = "" + IsFeatured = $False + Notes = "" + Owner = "" + PrivacyInformationUrl = "" + Publisher = "Contoso" + PublishingState = "published" + RoleScopeTagIds = @() + AdditionalProperties = @{ + '@odata.type' = '#microsoft.graph.webApp' + } + } + } + Mock -CommandName Get-MgBetaDeviceAppManagementMobileAppAssignment -MockWith{ + return $null + } + } + + 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