From 2265763cae5da07527e2b1b612a9eb48db0aae0f Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Fri, 1 Jul 2022 17:20:50 +0200 Subject: [PATCH 01/10] Fixing #2056 --- CHANGELOG.md | 6 ++++++ ...FT_TeamsMeetingBroadcastConfiguration.psm1 | 20 +++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afa42ef0d2..4658c696b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change log for Microsoft365DSC +# UNRELEASED + +* TeamsMeetingBroadcastConfiguration + * Fixing export issue where SdnApiToken is exported as a string instead of + a variable + # 1.22.824.1 * AADApplication diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsMeetingBroadcastConfiguration/MSFT_TeamsMeetingBroadcastConfiguration.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsMeetingBroadcastConfiguration/MSFT_TeamsMeetingBroadcastConfiguration.psm1 index 9dd86d9e7f..5fafd04611 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsMeetingBroadcastConfiguration/MSFT_TeamsMeetingBroadcastConfiguration.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsMeetingBroadcastConfiguration/MSFT_TeamsMeetingBroadcastConfiguration.psm1 @@ -48,7 +48,7 @@ function Get-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -68,7 +68,7 @@ function Get-TargetResource SdnApiTemplateUrl = $config.SdnApiTemplateUrl SdnApiToken = $config.SdnApiToken SupportURL = $config.SupportURL - Credential = $Credential + Credential = $Credential } } catch @@ -143,7 +143,7 @@ function Set-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -203,14 +203,14 @@ function Test-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters Add-M365DSCTelemetryEvent -Data $data #endregion - Write-Verbose -Message "Testing configuration of Teams Client" + Write-Verbose -Message "Testing configuration of Teams Meeting Broadcast" $CurrentValues = Get-TargetResource @PSBoundParameters @@ -253,7 +253,7 @@ function Export-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -264,7 +264,7 @@ function Export-TargetResource { $dscContent = '' $params = @{ - Identity = "Global" + Identity = "Global" Credential = $Credential } Add-ConfigurationDataEntry -Node "NonNodeData" -Key "SdnApiToken" -Value "**********"` @@ -272,7 +272,7 @@ function Export-TargetResource $results = Get-TargetResource @params $results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` -Results $Results - $results.SdnAPIToken = '$ConfigurationData.Settings.SdnApiToken' + $results.SdnApiToken = '$ConfigurationData.Settings.SdnApiToken' $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` -Results $Results @@ -281,6 +281,10 @@ function Export-TargetResource -ModulePath $PSScriptRoot ` -Results $Results ` -Credential $Credential + + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock ` + -ParameterName "SdnApiToken" + $dscContent += $currentDSCBlock Save-M365DSCPartialExport -Content $currentDSCBlock ` -FileName $Global:PartialExportFileName From 7bbca471db0b738aa3cff48b48f8eecaf856d848 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Fri, 1 Jul 2022 17:25:01 +0200 Subject: [PATCH 02/10] Partly solution for #2032 --- CHANGELOG.md | 3 +++ .../MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4658c696b8..40f0fe8faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ # UNRELEASED +* PlannerPlan + * Fix export issue where the export wasn't created correctly because of the + use of an incorrect property name. * TeamsMeetingBroadcastConfiguration * Fixing export issue where SdnApiToken is exported as a string instead of a variable diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 index 53d4e2e7bc..2ecbd51cc9 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 @@ -40,7 +40,7 @@ function Get-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -79,7 +79,7 @@ function Get-TargetResource BucketId = $bucket[0].Id Ensure = "Present" ApplicationId = $ApplicationId - TenantID = $TenantId + TenantId = $TenantId CertificateThumbprint = $CertificateThumbprint } Write-Verbose -Message "Get-TargetResource Result: `n $(Convert-M365DscHashtableToString -Hashtable $results)" @@ -152,7 +152,7 @@ function Set-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -226,7 +226,7 @@ function Test-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -275,7 +275,7 @@ function Export-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -289,16 +289,16 @@ function Export-TargetResource { [array]$groups = Get-MgGroup -All:$true -ErrorAction Stop - $ConnectionMode = Connect-Graph -Scopes "Group.ReadWrite.All" + $null = Connect-Graph -Scopes "Group.ReadWrite.All" $i = 1 $dscContent = '' Write-Host "`r`n" -NoNewline foreach ($group in $groups) { - Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.ObjectID)}" + Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.Id)}" try { - [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.ObjectId -ErrorAction 'SilentlyContinue' + [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id -ErrorAction 'SilentlyContinue' $j = 1 foreach ($plan in $plans) @@ -308,7 +308,7 @@ function Export-TargetResource $k = 1 foreach ($bucket in $buckets) { - Write-Host " [$k/$($buckets.Length)] $($bucket.Name)" -NoNewline + Write-Host " [$k/$($buckets.Length)] $($bucket.Name)" $params = @{ Name = $bucket.Name PlanId = $plan.Id From e7574d24331923340aaf4848419bd4d23faedc39 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Wed, 6 Jul 2022 09:38:45 +0200 Subject: [PATCH 03/10] Fixing #2032 --- .../MSFT_PlannerBucket.psm1 | 96 +++------ .../MSFT_PlannerBucket.schema.mof | 4 +- .../MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 | 202 +++++++----------- .../MSFT_PlannerPlan.schema.mof | 4 +- .../MSFT_PlannerTask/MSFT_PlannerTask.psm1 | 117 ++++------ .../MSFT_PlannerTask.schema.mof | 3 +- .../GraphHelpers/PlannerTaskObject.psm1 | 133 +++++++----- 7 files changed, 229 insertions(+), 330 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 index 2ecbd51cc9..c82ebbc490 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.psm1 @@ -21,17 +21,9 @@ function Get-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) Write-Verbose -Message "Getting configuration of Planner Bucket {$Name}" @@ -47,12 +39,13 @@ function Get-TargetResource Add-M365DSCTelemetryEvent -Data $data #endregion + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + $nullReturn = $PSBoundParameters $nullReturn.Ensure = "Absent" try { - Connect-Graph -Scopes "Group.ReadWrite.All" | Out-Null - if (-not [System.String]::IsNullOrEmpty($BucketId)) { [Array]$bucket = Get-MgPlannerPlanBucket -PlannerPlanId $PlanId | Where-Object -FilterScript { $_.Id -eq $BucketId } @@ -74,13 +67,11 @@ function Get-TargetResource } $results = @{ - Name = $Name - PlanId = $PlanId - BucketId = $bucket[0].Id - Ensure = "Present" - ApplicationId = $ApplicationId - TenantId = $TenantId - CertificateThumbprint = $CertificateThumbprint + Name = $Name + PlanId = $PlanId + BucketId = $bucket[0].Id + Ensure = "Present" + Credential = $Credential } Write-Verbose -Message "Get-TargetResource Result: `n $(Convert-M365DscHashtableToString -Hashtable $results)" return $results @@ -133,17 +124,9 @@ function Set-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) Write-Verbose -Message "Setting configuration of Planner Bucket {$Name}" @@ -159,13 +142,12 @@ function Set-TargetResource Add-M365DSCTelemetryEvent -Data $data #endregion - Connect-Graph -Scopes "Group.ReadWrite.All" | Out-Null + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters $SetParams = $PSBoundParameters $currentValues = Get-TargetResource @PSBoundParameters - $SetParams.Remove("ApplicationId") | Out-Null - $SetParams.Remove("TenantId") | Out-Null - $SetParams.Remove("CertificateThumbprint") | Out-Null + $SetParams.Remove("Credential") | Out-Null $SetParams.Remove("Ensure") | Out-Null if ($Ensure -eq 'Present' -and $currentValues.Ensure -eq 'Absent') @@ -209,17 +191,9 @@ function Test-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies @@ -239,9 +213,6 @@ function Test-TargetResource Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" $ValuesToCheck = $PSBoundParameters - $ValuesToCheck.Remove('ApplicationId') | Out-Null - $ValuesToCheck.Remove('TenantId') | Out-Null - $ValuesToCheck.Remove('CertificateThumbprint') | Out-Null $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` -Source $($MyInvocation.MyCommand.Source) ` -DesiredValues $PSBoundParameters ` @@ -259,16 +230,8 @@ function Export-TargetResource param ( [Parameter(Mandatory = $true)] - [System.String] - $ApplicationId, - - [Parameter(Mandatory = $true)] - [System.String] - $TenantId, - - [Parameter(Mandatory = $true)] - [System.String] - $CertificateThumbprint + [System.Management.Automation.PSCredential] + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies @@ -289,7 +252,6 @@ function Export-TargetResource { [array]$groups = Get-MgGroup -All:$true -ErrorAction Stop - $null = Connect-Graph -Scopes "Group.ReadWrite.All" $i = 1 $dscContent = '' Write-Host "`r`n" -NoNewline @@ -303,19 +265,17 @@ function Export-TargetResource $j = 1 foreach ($plan in $plans) { - Write-Host " [$j/$($plans.Length)] $($plan.Title)" + Write-Host " |---[$j/$($plans.Length)] $($plan.Title)" $buckets = Get-MgPlannerPlanBucket -PlannerPlanId $plan.Id $k = 1 foreach ($bucket in $buckets) { - Write-Host " [$k/$($buckets.Length)] $($bucket.Name)" + Write-Host " |---[$k/$($buckets.Length)] $($bucket.Name)" -NoNewline $params = @{ - Name = $bucket.Name - PlanId = $plan.Id - BucketId = $Bucket.Id - ApplicationId = $ApplicationId - TenantId = $TenantId - CertificateThumbprint = $CertificateThumbprint + Name = $bucket.Name + PlanId = $plan.Id + BucketId = $Bucket.Id + Credential = $Credential } $results = Get-TargetResource @params $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.schema.mof index 749b0e2999..513755b86a 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerBucket/MSFT_PlannerBucket.schema.mof @@ -5,8 +5,6 @@ class MSFT_PlannerBucket : OMI_BaseResource [Key, Description("Id of the Plan to which the bucket is associated with.")] string PlanId; [Write, Description("Id of the Bucket, if known.")] string BucketId; [Write, Description("Present ensures the Plan exists, absent ensures it is removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; - [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; - [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Required, Description("Credentials of the account to authenticate with."), EmbeddedInstance("MSFT_Credential")] string Credential; }; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 index 0d43f2c113..ced0c6a076 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 @@ -17,17 +17,9 @@ function Get-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) Write-Verbose -Message "Getting configuration of Planner Plan {$Title}" @@ -36,7 +28,7 @@ function Get-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -45,6 +37,7 @@ function Get-TargetResource $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` -InboundParameters $PSBoundParameters + $nullReturn = $PSBoundParameters $nullReturn.Ensure = "Absent" try @@ -72,22 +65,19 @@ function Get-TargetResource Write-Verbose -Message "Multiple Groups with name {$OwnerGroup} found." } - Write-Verbose -Message "Connecting to the Microsoft Graph" - $ConnectionMode = Connect-Graph -Scopes "Group.ReadWrite.All" - $plan = $null foreach ($group in $AllGroups) { try { Write-Verbose -Message "Scanning Group {$($group.DisplayName)} for plan {$Title}" - $plan = Get-MgGroupPlannerPlan -GroupId $group.ObjectId | Where-Object -FilterScript { $_.Title -eq $Title } + $plan = Get-MgGroupPlannerPlan -GroupId $group.Id | Where-Object -FilterScript { $_.Title -eq $Title } if ($null -ne $plan) { Write-Verbose -Message "Found Plan." if ($UsedID) { - $OwnerGroupValue = $group.ObjectId + $OwnerGroupValue = $group.Id } else { @@ -130,12 +120,10 @@ function Get-TargetResource { Write-Verbose -Message "Plan found, returning Ensure = Present" $results = @{ - Title = $Title - OwnerGroup = $OwnerGroupValue - Ensure = 'Present' - CertificateThumbprint = $CertificateThumbprint - ApplicationId = $ApplicationId - TenantID = $TenantId + Title = $Title + OwnerGroup = $OwnerGroupValue + Ensure = 'Present' + Credential = $Credential } } Write-Verbose -Message "Get-TargetResource Result: `n $(Convert-M365DscHashtableToString -Hashtable $results)" @@ -185,17 +173,9 @@ function Set-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) Write-Verbose -Message "Setting configuration of Planner Plan {$Title}" @@ -204,20 +184,19 @@ function Set-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters Add-M365DSCTelemetryEvent -Data $data #endregion - Connect-Graph -Scopes "Group.ReadWrite.All" | Out-Null + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters $SetParams = $PSBoundParameters $currentValues = Get-TargetResource @PSBoundParameters - $SetParams.Remove("ApplicationId") | Out-Null - $SetParams.Remove("TenantId") | Out-Null - $SetParams.Remove("CertificateThumbprint") | Out-Null + $SetParams.Remove("Credential") | Out-Null $SetParams.Remove("Ensure") | Out-Null if ($Ensure -eq 'Present' -and $currentValues.Ensure -eq 'Absent') @@ -235,9 +214,9 @@ function Set-TargetResource { [Array]$AllGroups = Get-MgGroup -Search $OwnerGroup } - $plan = Get-MgGroupPlannerPlan -GroupId $AllGroups[0].ObjectId | Where-Object -FilterScript { $_.Title -eq $Title } + $plan = Get-MgGroupPlannerPlan -GroupId $AllGroups[0].Id | Where-Object -FilterScript { $_.Title -eq $Title } $SetParams.Add("PlannerPlanId", $plan.Id) - $SetParams.Add("Owner", $AllGroups[0].ObjectId) + $SetParams.Add("Owner", $AllGroups[0].Id) $SetParams.Remove("OwnerGroup") | Out-Null Update-MgPlannerPlan @SetParams } @@ -266,24 +245,16 @@ function Test-TargetResource [ValidateSet("Present", "Absent")] $Ensure = 'Present', - [Parameter()] - [System.String] - $ApplicationId, - - [Parameter()] - [System.String] - $TenantId, - - [Parameter()] - [System.String] - $CertificateThumbprint + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -296,9 +267,6 @@ function Test-TargetResource Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" $ValuesToCheck = $PSBoundParameters - $ValuesToCheck.Remove('ApplicationId') | Out-Null - $ValuesToCheck.Remove('TenantId') | Out-Null - $ValuesToCheck.Remove('CertificateThumbprint') | Out-Null $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` -Source $($MyInvocation.MyCommand.Source) ` -DesiredValues $PSBoundParameters ` @@ -315,28 +283,16 @@ function Export-TargetResource [OutputType([System.String])] param ( - [Parameter()] - [System.String] - $Filter, - - [Parameter(Mandatory = $true)] - [System.String] - $ApplicationId, - [Parameter(Mandatory = $true)] - [System.String] - $TenantId, - - [Parameter(Mandatory = $true)] - [System.String] - $CertificateThumbprint + [System.Management.Automation.PSCredential] + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -350,68 +306,36 @@ function Export-TargetResource { [array]$groups = Get-MgGroup -All:$true -ErrorAction Stop - $ConnectionMode = Connect-Graph -Scopes "Group.ReadWrite.All" $i = 1 $dscContent = '' + Write-Host "`r`n" -NoNewline foreach ($group in $groups) { - Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.ObjectID)}" + Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.Id)}" try { - [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.ObjectId -ErrorAction 'SilentlyContinue' -All:$true -Filter $Filter + [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id -ErrorAction 'SilentlyContinue' $j = 1 foreach ($plan in $plans) - { - $params = @{ - Title = $plan.Title - OwnerGroup = $group.ObjectId - ApplicationId = $ApplicationId - TenantId = $TenantId - CertificateThumbprint = $CertificateThumbprint - } - Write-Host " [$j/$($plans.Length)] $($plan.Title)" - $results = Get-TargetResource @params - $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` - -Results $Results - $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` - -ConnectionMode $ConnectionMode ` - -ModulePath $PSScriptRoot ` - -Results $Results ` - -Credential $Credential - $dscContent += $currentDSCBlock - - Save-M365DSCPartialExport -Content $currentDSCBlock ` - -FileName $Global:PartialExportFileName - $j++ - } - $i++ - } - catch - { - try - { - Write-Verbose -Message $_ - $tenantIdValue = "" - if (-not [System.String]::IsNullOrEmpty($TenantId)) - { - $tenantIdValue = $TenantId - } - elseif ($null -ne $Credential) - { - $tenantIdValue = $Credential.UserName.Split('@')[1] - } - Add-M365DSCEvent -Message $_ -EntryType 'Error' ` - -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` - -TenantId $tenantIdValue - } - catch - { - Write-Verbose -Message $_ - } + Write-Host " |---[$j/$($plans.Length)] $($plle)" + Credential = $Credential } + $results = Get-TargetResource @params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + $dscContent += $currentDSCBlock + + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $j++ } - return $dscContent + $i++ } catch { @@ -435,6 +359,40 @@ function Export-TargetResource { Write-Verbose -Message $_ } + } +} +return $dscContent +} +catch +{ + try + { + Write-Verbose -Message $_ + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $Credential) + { + $tenantIdValue = $Credential.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + return "" +} +} + +Export-ModuleMember -Function *-TargetResource + { + Write-Verbose -Message $_ + } return "" } } diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.schema.mof index 93f78f93e8..5dbccd6e3b 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.schema.mof @@ -4,8 +4,6 @@ class MSFT_PlannerPlan : OMI_BaseResource [Key, Description("The Title of the Planner Plan.")] string Title; [Key, Description("Name of Id of the Azure Active Directory Group who owns the plan")] string OwnerGroup; [Write, Description("Present ensures the Plan exists, absent ensures it is removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; - [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; - [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Required, Description("Credentials of the account to authenticate with."), EmbeddedInstance("MSFT_Credential")] string Credential; }; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 index 93de5ef912..8e97e492e0 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 @@ -70,11 +70,7 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter()] - [System.String] - $ApplicationId + $Credential ) Write-Verbose -Message "Getting configuration of Planner Task {$Title}" @@ -83,7 +79,7 @@ function Get-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -95,6 +91,9 @@ function Get-TargetResource try { + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + # If no TaskId were passed, automatically assume that this is a new task; if ([System.String]::IsNullOrEmpty($TaskId)) { @@ -115,7 +114,7 @@ function Get-TargetResource } $task = [PlannerTaskObject]::new() Write-Verbose -Message "Populating task {$taskId} from the Get method" - $task.PopulateById($Credential, $ApplicationId, $TaskId) + $task.PopulateById($Credential, $TaskId) if ($null -eq $task) { @@ -128,8 +127,6 @@ function Get-TargetResource #region Task Assignment if ($task.Assignments.Length -gt 0) { - $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` - -InboundParameters $PSBoundParameters $assignedValues = @() foreach ($assignee in $task.Assignments) { @@ -173,8 +170,7 @@ function Get-TargetResource DueDateTime = $DueDateTimeValue Notes = $NotesValue Ensure = "Present" - ApplicationId = $ApplicationId - Credential = $Credential + Credential = $Credential } Write-Verbose -Message "Get-TargetResource Result: `n $(Convert-M365DscHashtableToString -Hashtable $results)" return $results @@ -277,11 +273,7 @@ function Set-TargetResource [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter()] - [System.String] - $ApplicationId + $Credential ) Write-Verbose -Message "Setting configuration of Planner Task {$Title}" @@ -290,14 +282,15 @@ function Set-TargetResource #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters Add-M365DSCTelemetryEvent -Data $data #endregion - Connect-Graph -Scopes "Group.ReadWrite.All" | Out-Null + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters $currentValues = Get-TargetResource @PSBoundParameters @@ -318,7 +311,7 @@ function Set-TargetResource if (-not [System.String]::IsNullOrEmpty($TaskId)) { Write-Verbose -Message "Populating Task {$TaskId} from the Set method" - $task.PopulateById($Credential, $ApplicationId, $TaskId) + $task.PopulateById($Credential, $TaskId) } $task.BucketId = $Bucket @@ -333,15 +326,13 @@ function Set-TargetResource #region Assignments if ($AssignedUsers.Length -gt 0) { - $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` - -InboundParameters $PSBoundParameters $AssignmentsValue = @() foreach ($userName in $AssignedUsers) { - $user = Get-MgUser -Search $userName + $user = Get-MgUser -UserId $userName if ($null -ne $user) { - $AssignmentsValue += $user.ObjectId + $AssignmentsValue += $user.Id } } $task.Assignments = $AssignmentsValue @@ -396,20 +387,20 @@ function Set-TargetResource if ($Ensure -eq 'Present' -and $currentValues.Ensure -eq 'Absent') { Write-Verbose -Message "Planner Task {$Title} doesn't already exist. Creating it." - $task.Create($Credential, $ApplicationId) + $task.Create($Credential) } elseif ($Ensure -eq 'Present' -and $currentValues.Ensure -eq 'Present') { Write-Verbose -Message "Planner Task {$Title} already exists, but is not in the ` Desired State. Updating it." - $task.Update($Credential, $ApplicationId) + $task.Update($Credential) #endregion } elseif ($Ensure -eq 'Absent' -and $currentValues.Ensure -eq 'Present') { Write-Verbose -Message "Planner Task {$Title} exists, but is should not. ` Removing it." - $task.Delete($Credential, $ApplicationId, $TaskId) + $task.Delete($Credential, $TaskId) } } @@ -485,18 +476,14 @@ function Test-TargetResource [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter()] - [System.String] - $ApplicationId + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -509,8 +496,6 @@ function Test-TargetResource Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" $ValuesToCheck = $PSBoundParameters - $ValuesToCheck.Remove('ApplicationId') | Out-Null - $ValuesToCheck.Remove('Credential') | Out-Null # If the Task is currently assigned to a bucket and the Bucket property is null, # assume that we are trying to remove the given task from the bucket and therefore @@ -547,18 +532,14 @@ function Export-TargetResource ( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter()] - [System.String] - $ApplicationId + $Credential ) #Ensure the proper dependencies are installed in the current environment. Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName -replace "MSFT_", "" - $CommandName = $MyInvocation.MyCommand + $CommandName = $MyInvocation.MyCommand $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` -CommandName $CommandName ` -Parameters $PSBoundParameters @@ -574,41 +555,39 @@ function Export-TargetResource $i = 1 $dscContent = '' + Write-Host "`r`n" -NoNewline foreach ($group in $groups) { - Write-Host " |---[$i/$($groups.Length)] $($group.DisplayName) - {$($group.ObjectID)}" + Write-Host " |---[$i/$($groups.Length)] $($group.DisplayName) - {$($group.Id)}" try { - [Array]$plans = Get-M365DSCPlannerPlansFromGroup -GroupId $group.ObjectId ` - -Credential $Credential ` - -ApplicationId $ApplicationId + [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id -ErrorAction 'SilentlyContinue' + # [Array]$plans = Get-M365DSCPlannerPlansFromGroup -GroupId $group.Id ` + # -Credential $Credential $j = 1 foreach ($plan in $plans) { Write-Host " |---[$j/$($plans.Length)] $($plan.Title)" - [Array]$tasks = Get-M365DSCPlannerTasksFromPlan -PlanId $plan.Id ` - -Credential $Credential ` - -ApplicationId $ApplicationId + [Array]$tasks = Get-MgGroupPlannerPlanTask -GroupId $group.Id -PlannerPlanId $plan.Id -ErrorAction 'SilentlyContinue' + # [Array]$tasks = Get-M365DSCPlannerTasksFromPlan -PlanId ` + # -Credential $Credential $k = 1 foreach ($task in $tasks) { - Write-Host " [$k/$($tasks.Length)] $($task.Title)" -NoNewline + Write-Host " |---[$k/$($tasks.Length)] $($task.Title)" -NoNewline + $currentDSCBlock = "" + $params = @{ - TaskId = $task.Id - PlanId = $plan.Id - Title = $task.Title - ApplicationId = $ApplicationId + TaskId = $task.Id + PlanId = $plan.Id + Title = $task.Title Credential = $Credential } $result = Get-TargetResource @params - if ([System.String]::IsNullOrEmpty($result.ApplicationId)) - { - $result.Remove("ApplicationId") | Out-Null - } if ($result.AssignedUsers.Count -eq 0) { $result.Remove("AssignedUsers") | Out-Null @@ -642,7 +621,7 @@ function Export-TargetResource $currentDSCBlock += " PlannerTask " + (New-Guid).ToString() + "`r`n" $currentDSCBlock += " {`r`n" $content = Get-DSCBlock -Params $result -ModulePath $PSScriptRoot - $content = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock ` + $content = Convert-DSCStringParamToVariable -DSCBlock $content ` -ParameterName "Credential" if ($result.Attachments.Length -gt 0) { @@ -659,14 +638,6 @@ function Export-TargetResource $currentDSCBlock += $content $currentDSCBlock += " }`r`n" $dscContent += $currentDSCBlock - $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` - -Results $Results - $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` - -ConnectionMode $ConnectionMode ` - -ModulePath $PSScriptRoot ` - -Results $Results ` - -Credential $Credential - $dscContent += $currentDSCBlock Save-M365DSCPartialExport -Content $currentDSCBlock ` -FileName $Global:PartialExportFileName @@ -825,16 +796,11 @@ function Get-M365DSCPlannerPlansFromGroup [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter(Mandatory = $true)] - [System.String] - $ApplicationId + $Credential ) $results = @() $uri = "https://graph.microsoft.com/v1.0/groups/$GroupId/planner/plans" $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` -Uri $uri ` -Method Get foreach ($plan in $taskResponse.value) @@ -858,16 +824,11 @@ function Get-M365DSCPlannerTasksFromPlan [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] - $Credential, - - [Parameter(Mandatory = $true)] - [System.String] - $ApplicationId + $Credential ) $results = @() $uri = "https://graph.microsoft.com/v1.0/planner/plans/$PlanId/tasks" - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method Get foreach ($task in $taskResponse.value) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.schema.mof index ece81066db..c374df7fcc 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.schema.mof @@ -29,7 +29,6 @@ class MSFT_PlannerTask : OMI_BaseResource [Write, Description("Priority of the Task. Value can only be between 1 and 10.")] UInt32 Priority; [Write, Description("Id of the group conversation thread associated with the comments section for this task.")] String ConversationThreadId; [Write, Description("Present ensures the Plan exists, absent ensures it is removed"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; - [Required, Description("Credentials of the Global Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; + [Required, Description("Credentials of the account to authenticate with."), EmbeddedInstance("MSFT_Credential")] string Credential; }; diff --git a/Modules/Microsoft365DSC/Modules/GraphHelpers/PlannerTaskObject.psm1 b/Modules/Microsoft365DSC/Modules/GraphHelpers/PlannerTaskObject.psm1 index 9b5adee92b..ea3e9982e3 100644 --- a/Modules/Microsoft365DSC/Modules/GraphHelpers/PlannerTaskObject.psm1 +++ b/Modules/Microsoft365DSC/Modules/GraphHelpers/PlannerTaskObject.psm1 @@ -20,41 +20,63 @@ class PlannerTaskObject [string]GetTaskCategoryNameByColor([string]$ColorName) { - switch($ColorName) + switch ($ColorName) { - "Pink"{return "category1"} - "Red"{return "category2"} - "Yellow"{return "category3"} - "Green"{return "category4"} - "Blue"{return "category5"} - "Purple"{return "category6"} + "Pink" + { return "category1" + } + "Red" + { return "category2" + } + "Yellow" + { return "category3" + } + "Green" + { return "category4" + } + "Blue" + { return "category5" + } + "Purple" + { return "category6" + } } return $null } [string]GetTaskColorNameByCategory([string]$CategoryName) { - switch($CategoryName) + switch ($CategoryName) { - "category1"{return "Pink"} - "category2"{return "Red"} - "category3"{return "Yellow"} - "category4"{return "Green"} - "category5"{return "Blue"} - "category6"{return "Purple"} + "category1" + { return "Pink" + } + "category2" + { return "Red" + } + "category3" + { return "Yellow" + } + "category4" + { return "Green" + } + "category5" + { return "Blue" + } + "category6" + { return "Purple" + } } return $null } - [void]PopulateById([System.Management.Automation.PSCredential]$Credential, [String]$ApplicationId, [string]$TaskId) + [void]PopulateById([System.Management.Automation.PSCredential]$Credential, [string]$TaskId) { $uri = "https://graph.microsoft.com/beta/planner/tasks/$TaskId" - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method Get - $taskDetailsResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskDetailsResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri ($uri + "/details") ` -Method Get @@ -62,7 +84,9 @@ class PlannerTaskObject $assignmentsValue = @() if ($null -ne $taskResponse.assignments) { - $allAssignments = $taskResponse.assignments | gm | Where-Object -FilterScript{$_.MemberType -eq 'NoteProperty'} + $allAssignments = $taskResponse.assignments | Get-Member | Where-Object -FilterScript { + $_.MemberType -eq 'NoteProperty' + } foreach ($assignment in $allAssignments) { $assignmentsValue += $assignment.Name @@ -74,7 +98,9 @@ class PlannerTaskObject $attachmentsValue = @() if ($null -ne $taskDetailsResponse.references) { - $allAttachments = $taskDetailsResponse.references | gm | Where-Object -FilterScript{$_.MemberType -eq 'NoteProperty'} + $allAttachments = $taskDetailsResponse.references | Get-Member | Where-Object -FilterScript { + $_.MemberType -eq 'NoteProperty' + } foreach ($attachment in $allAttachments) { $hashEntry = @{ @@ -91,7 +117,9 @@ class PlannerTaskObject $categoriesValue = @() if ($null -ne $taskResponse.appliedCategories) { - $allCategories = $taskResponse.appliedCategories | gm | Where-Object -FilterScript{$_.MemberType -eq 'NoteProperty'} + $allCategories = $taskResponse.appliedCategories | Get-Member | Where-Object -FilterScript { + $_.MemberType -eq 'NoteProperty' + } foreach ($category in $allCategories) { $categoriesValue += $this.GetTaskColorNameByCategory($category.Name) @@ -103,7 +131,9 @@ class PlannerTaskObject $checklistValue = @() if ($null -ne $taskDetailsResponse.checklist) { - $allCheckListItems = $taskDetailsResponse.checklist | gm | Where-Object -FilterScript{$_.MemberType -eq 'NoteProperty'} + $allCheckListItems = $taskDetailsResponse.checklist | Get-Member | Where-Object -FilterScript { + $_.MemberType -eq 'NoteProperty' + } foreach ($checkListItem in $allCheckListItems) { $hashEntry = @{ @@ -114,22 +144,23 @@ class PlannerTaskObject } } #endregion - $this.Etag = $taskResponse.'@odata.etag' - $this.Title = $taskResponse.title - $this.StartDateTime = $taskResponse.startDateTime + $this.Etag = $taskResponse.'@odata.etag' + $this.Title = $taskResponse.title + $this.StartDateTime = $taskResponse.startDateTime $this.ConversationThreadId = $taskResponse.conversationThreadId - $this.DueDateTime = $taskResponse.dueDateTime - $this.CompletedDateTime = $taskResponse.completedDateTime - $this.PlanId = $taskResponse.planId - $this.TaskId = $taskResponse.id - $this.BucketId = $taskResponse.bucketId - $this.Priority = $taskResponse.priority - $this.Notes = $taskDetailsResponse.description - $this.Assignments = $assignmentsValue - $this.Attachments = $attachmentsValue - $this.Categories = $categoriesValue - $this.Checklist = $checklistValue + $this.DueDateTime = $taskResponse.dueDateTime + $this.CompletedDateTime = $taskResponse.completedDateTime + $this.PlanId = $taskResponse.planId + $this.TaskId = $taskResponse.id + $this.BucketId = $taskResponse.bucketId + $this.Priority = $taskResponse.priority + $this.Notes = $taskDetailsResponse.description + $this.Assignments = $assignmentsValue + $this.Attachments = $attachmentsValue + $this.Categories = $categoriesValue + $this.Checklist = $checklistValue } + [string]ConvertToJSONTask() { $sb = [System.Text.StringBuilder]::New() @@ -247,12 +278,11 @@ class PlannerTaskObject return $sb.ToString() } - [void]Create([System.Management.Automation.PSCredential]$Credential, [String]$ApplicationId) + [void]Create([System.Management.Automation.PSCredential]$Credential) { $uri = "https://graph.microsoft.com/v1.0/planner/tasks" $body = $this.ConvertToJSONTask() - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "POST" ` -Body $body @@ -261,56 +291,51 @@ class PlannerTaskObject $this.UpdateDetails($Credential) } - [void]Update([System.Management.Automation.PSCredential]$Credential, [String]$ApplicationId) + [void]Update([System.Management.Automation.PSCredential]$Credential) { - $uri = "https://graph.microsoft.com/beta/planner/tasks/$($this.TaskId)" + $uri = "https://graph.microsoft.com/v1.0/planner/tasks/$($this.TaskId)" $body = $this.ConvertToJSONTask() $Headers = @{} $Headers.Add("If-Match", $this.ETag) - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "PATCH" ` -Body $body ` -Headers $Headers } - [void]UpdateDetails([System.Management.Automation.PSCredential]$Credential, [String]$ApplicationId) + [void]UpdateDetails([System.Management.Automation.PSCredential]$Credential) { $uri = "https://graph.microsoft.com/v1.0/planner/tasks/$($this.TaskId)/details" $body = $this.ConvertToJSONTaskDetails() # Get ETag for the details - $currentTaskDetails = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $currentTaskDetails = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "GET" $Headers = @{} $Headers.Add("If-Match", $currentTaskDetails.'@odata.etag') - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "PATCH" ` -Body $body ` -Headers $Headers } - [void]Delete([System.Management.Automation.PSCredential]$Credential, [string]$ApplicationId, [string]$TaskId) + [void]Delete([System.Management.Automation.PSCredential]$Credential, [string]$TaskId) { $VerbosePreference = 'Continue' Write-Verbose -Message "Initiating the Deletion of Task {$TaskId}" $uri = "https://graph.microsoft.com/v1.0/planner/tasks/$TaskId" # Get ETag for the details - $currentTaskDetails = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $currentTaskDetails = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "GET" $Headers = @{} $Headers.Add("If-Match", $currentTaskDetails.'@odata.etag') Write-Verbose -Message "Retrieved Task's ETag {$($currentTaskDetails.'@odata.etag')}" - $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -CloudCredential $Credential ` - -ApplicationId $ApplicationId ` + $taskResponse = Invoke-MSCloudLoginMicrosoftGraphAPI -Credential $Credential ` -Uri $uri ` -Method "DELETE" ` -Headers $Headers From 798fdb7e9f0dd653f966ff5727dbef96bed52a49 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Wed, 6 Jul 2022 11:00:17 +0200 Subject: [PATCH 04/10] Fixes issues #2037, #2032, #1979 and #1759 --- CHANGELOG.md | 15 + .../Modules/M365DSCReverse.psm1 | 467 +++++++++--------- .../Microsoft365DSC/Modules/M365DSCUtil.psm1 | 406 ++++++++++----- 3 files changed, 530 insertions(+), 358 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40f0fe8faa..fb5bcf534f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,27 @@ # UNRELEASED +* PlannerBucket + * Changed authentication method to Credentials only, since the Planner Graph API + does not support anything else * PlannerPlan * Fix export issue where the export wasn't created correctly because of the use of an incorrect property name. + * Changed authentication method to Credentials only, since the Planner Graph API + does not support anything else +* PlannerTask + * Changed authentication method to Credentials only, since the Planner Graph API + does not support anything else * TeamsMeetingBroadcastConfiguration * Fixing export issue where SdnApiToken is exported as a string instead of a variable +* MISC + * Updated Export functionality to only export the LCM settings when the + executed as Administrator + * Added support for multiple authentication methods to the Export functionality. + The code now uses the most secure method that is provided in the command line + and that supported by the specified resources in the following order: + Certificate Thumbprint, Certificate Path, Application Secret, Credential # 1.22.824.1 diff --git a/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 index 1ccbe4e0ac..90e3475d36 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 @@ -133,58 +133,22 @@ function Start-M365DSCConfigurationExtract { $AuthMethods += "Credentials" } - if (-not [System.String]::IsNullOrEmpty($CertificateThumbprint) -or ` - -not [System.String]::IsNullOrEmpty($CertificatePassword) -or ` - -not [System.String]::IsNullOrEmpty($CertificatePath)) + if (-not [System.String]::IsNullOrEmpty($CertificateThumbprint)) { - $AuthMethods += "Certificate" - } - if (-not [System.String]::IsNullOrEmpty($ApplicationSecret)) - { - $AuthMethods += "ApplicationWithSecret" + $AuthMethods += "CertificateThumbprint" } - $ResourcesPath = Join-Path -Path $PSScriptRoot ` - -ChildPath "..\DSCResources\" ` - -Resolve - $AllResources = Get-ChildItem $ResourcesPath -Recurse | Where-Object { $_.Name -like 'MSFT_*.psm1' } - if (!$AllResources) + if (-not [System.String]::IsNullOrEmpty($CertificatePath)) { - Write-Host "Resource files were not found, aborting" - break + $AuthMethods += "CertificatePath" } - $i = 1 - $ResourcesToExport = @() - $ResourcesPath = @() - foreach ($ResourceModule in $AllResources) - { - try - { - $resourceName = $ResourceModule.Name.Split('.')[0] -replace 'MSFT_', '' - - if ((($Components -and ($Components -contains $resourceName)) -or $AllComponents -or ` - (-not $Components -and $null -eq $Workloads)) -and ` - ($ComponentsSpecified -or ($ComponentsToSkip -notcontains $resourceName)) -and ` - $resourcesNotSupported -notcontains $resourceName) - { - $ResourcesToExport += $ResourceName - $ResourcesPath += $ResourceModule - } - } - catch - { - New-M365DSCLogEntry -Error $_ -Message $ResourceModule.Name -Source "[M365DSCReverse]$($ResourceModule.Name)" - } - } - if (!$ResourcesToExport) + if (-not [System.String]::IsNullOrEmpty($ApplicationSecret)) { - Write-Host "There are no valid resources to export, aborting" - break + $AuthMethods += "ApplicationWithSecret" } - $allSupportedResources = Get-M365DSCComponentsForAuthenticationType -AuthenticationMethod $AuthMethods ` - -ResourcesToExport $ResourcesToExport + $allSupportedResourcesWithMostSecureAuthMethod = Get-M365DSCComponentsWithMostSecureAuthenticationType -AuthenticationMethod $AuthMethods # If some resources are not supported based on the Authentication parameters # received, write a warning. @@ -214,7 +178,7 @@ function Start-M365DSCConfigurationExtract { $selectedResources = @() } - [Array]$compareResourcesResult = Compare-Object -ReferenceObject $allSupportedResources ` + [Array]$compareResourcesResult = Compare-Object -ReferenceObject $allSupportedResourcesWithMostSecureAuthMethod.Resource ` -DifferenceObject $selectedResources | Where-Object -FilterScript { $_.SideIndicator -eq '=>' } } catch @@ -248,58 +212,61 @@ function Start-M365DSCConfigurationExtract # Get Tenant Info $organization = "" - $principal = "" # Principal represents the "NetBios" name of the tenant (e.g. the M365DSC part of M365DSC.onmicrosoft.com) - - if ($AuthMethods -Contains 'Application') + if ($AuthMethods -contains 'CertificateThumbprint' -or ` + $AuthMethods -contains 'CertificatePath' -or ` + $AuthMethods -contains 'ApplicationWithSecret') { - $ConnectionMode = 'ServicePrincipal' $organization = Get-M365DSCTenantDomain -ApplicationId $ApplicationId ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint - } - elseif ($AuthMethods -Contains 'Certificate') - { - $ConnectionMode = 'ServicePrincipal' - $organization = $TenantId + -TenantId $TenantId } elseif ($AuthMethods -Contains 'Credentials') { - $ConnectionMode = 'Credentials' if ($null -ne $Credential -and $Credential.UserName.Contains("@")) { $organization = $Credential.UserName.Split("@")[1] } } - if ($organization.IndexOf(".") -gt 0) - { - $principal = $organization.Split(".")[0] - } + $AzureAutomation = $false [array] $version = Get-Module 'Microsoft365DSC' $version = $version[0].Version $DSCContent = [System.Text.StringBuilder]::New() $DSCContent.Append("# Generated with Microsoft365DSC version $version`r`n") | Out-Null $DSCContent.Append("# For additional information on how to use Microsoft365DSC, please visit https://aka.ms/M365DSC`r`n") | Out-Null - if ($ConnectionMode -eq 'Credentials') - { - $DSCContent.Append("param (`r`n") | Out-Null - $DSCContent.Append(" [parameter()]`r`n") | Out-Null - $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null - $DSCContent.Append(" `$Credential`r`n") | Out-Null - $DSCContent.Append(")`r`n`r`n") | Out-Null - } - else + $DSCContent.Append("param (`r`n") | Out-Null + + # Add script parameters, only add PSCredential parameters. All other information + # is placed in the Configuration Data file. + $newline = $false + switch ($AuthMethods) { - if (-not [System.String]::IsNullOrEmpty($CertificatePassword)) + "CertificatePath" { - $DSCContent.Append("param (`r`n") | Out-Null + if ($newline) + { + $DSCContent.Append("`r`n") | Out-Null + } $DSCContent.Append(" [parameter()]`r`n") | Out-Null $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null $DSCContent.Append(" `$CertificatePassword`r`n") | Out-Null - $DSCContent.Append(")`r`n`r`n") | Out-Null + $newline = $true + } + "Credentials" + { + if ($newline) + { + $DSCContent.Append("`r`n") | Out-Null + } + $DSCContent.Append(" [parameter()]`r`n") | Out-Null + $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null + $DSCContent.Append(" `$Credential`r`n") | Out-Null + $newline = $true } } + $DSCContent.Append(")`r`n`r`n") | Out-Null + + # Create Configuration section if (-not [System.String]::IsNullOrEmpty($FileName)) { $FileParts = $FileName.Split('.') @@ -315,85 +282,112 @@ function Start-M365DSCConfigurationExtract } $DSCContent.Append("Configuration $ConfigurationName`r`n{`r`n") | Out-Null - if ($ConnectionMode -eq 'Credentials') - { - $DSCContent.Append(" param (`r`n") | Out-Null - $DSCContent.Append(" [parameter()]`r`n") | Out-Null - $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null - $DSCContent.Append(" `$Credential`r`n") | Out-Null - $DSCContent.Append(" )`r`n`r`n") | Out-Null - $DSCContent.Append(" if (`$null -eq `$Credential)`r`n") | Out-Null - $DSCContent.Append(" {`r`n") | Out-Null - $DSCContent.Append(" <# Credentials #>`r`n") | Out-Null - $DSCContent.Append(" }`r`n") | Out-Null - $DSCContent.Append(" else`r`n") | Out-Null - $DSCContent.Append(" {`r`n") | Out-Null - $DSCContent.Append(" `$CredsCredential = `$Credential`r`n") | Out-Null - $DSCContent.Append(" }`r`n`r`n") | Out-Null - $DSCContent.Append(" `$OrganizationName = `$CredsCredential.UserName.Split('@')[1]`r`n") | Out-Null - } - else + # Adding Parameter section + $DSCContent.Append(" param (`r`n") | Out-Null + + $newline = $false + $postParamContent = [System.Text.StringBuilder]::New() + switch ($AuthMethods) { - if (-not [System.String]::IsNullOrEmpty($CertificatePassword)) - { - $DSCContent.Append(" param (`r`n") | Out-Null - $DSCContent.Append(" [parameter()]`r`n") | Out-Null - $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null - $DSCContent.Append(" `$CertificatePassword`r`n") | Out-Null - $DSCContent.Append(" )`r`n`r`n") | Out-Null - $DSCContent.Append(" if (`$null -eq `$CertificatePassword)`r`n") | Out-Null - $DSCContent.Append(" {`r`n") | Out-Null - $DSCContent.Append(" <# Credentials #>`r`n") | Out-Null - $DSCContent.Append(" }`r`n") | Out-Null - $DSCContent.Append(" else`r`n") | Out-Null - $DSCContent.Append(" {`r`n") | Out-Null - $DSCContent.Append(" `$CredsCertificatePassword = `$CertificatePassword`r`n") | Out-Null - $DSCContent.Append(" }`r`n`r`n") | Out-Null - } - - $DSCContent.Append(" `$OrganizationName = `$ConfigurationData.NonNodeData.OrganizationName`r`n") | Out-Null - Add-ConfigurationDataEntry -Node "NonNodeData" ` - -Key "OrganizationName" ` - -Value $organization ` - -Description "Tenant's default verified domain name" - Add-ConfigurationDataEntry -Node "NonNodeData" ` - -Key "ApplicationId" ` - -Value $ApplicationId ` - -Description "Azure AD Application Id for Authentication" - if (-not [System.String]::IsNullOrEmpty($TenantId)) + { $_ -in "CertificateThumbprint", "CertificatePath", "ApplicationWithSecret" } { + $postParamContent.Append(" `$OrganizationName = `$ConfigurationData.NonNodeData.OrganizationName`r`n") | Out-Null + + Add-ConfigurationDataEntry -Node "NonNodeData" ` + -Key "OrganizationName" ` + -Value $organization ` + -Description "Tenant's default verified domain name" + Add-ConfigurationDataEntry -Node "NonNodeData" ` + -Key "ApplicationId" ` + -Value $ApplicationId ` + -Description "Azure AD Application Id for Authentication" Add-ConfigurationDataEntry -Node "NonNodeData" ` -Key "TenantId" ` -Value $TenantId ` -Description "The Id or Name of the tenant to authenticate against" } - - if (-not [System.String]::IsNullOrEmpty($ApplicationSecret)) + "CertificateThumbprint" { Add-ConfigurationDataEntry -Node "NonNodeData" ` - -Key "ApplicationSecret" ` - -Value $ApplicationSecret ` - -Description "Azure AD Application Secret for Authentication" + -Key "CertificateThumbprint" ` + -Value $CertificateThumbprint ` + -Description "Thumbprint of the certificate to use for authentication" } - - if (-not [System.String]::IsNullOrEmpty($CertificatePath)) + "CertificatePath" { + if ($newline) + { + $DSCContent.Append("`r`n") | Out-Null + } + $DSCContent.Append(" [parameter()]`r`n") | Out-Null + $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null + $DSCContent.Append(" `$CertificatePassword`r`n") | Out-Null + + if ($newline) + { + $postParamContent.Append("`r`n") | Out-Null + } + $postParamContent.Append(" if (`$null -eq `$CertificatePassword)`r`n") | Out-Null + $postParamContent.Append(" {`r`n") | Out-Null + $postParamContent.Append(" <# Credentials #>`r`n") | Out-Null + $postParamContent.Append(" }`r`n") | Out-Null + $postParamContent.Append(" else`r`n") | Out-Null + $postParamContent.Append(" {`r`n") | Out-Null + $postParamContent.Append(" `$CredsCertificatePassword = `$CertificatePassword`r`n") | Out-Null + $postParamContent.Append(" }`r`n`r`n") | Out-Null + Add-ConfigurationDataEntry -Node "NonNodeData" ` -Key "CertificatePath" ` -Value $CertificatePath ` -Description "Local path to the .pfx certificate to use for authentication" - } - if (-not [System.String]::IsNullOrEmpty($CertificateThumbprint)) + $newline = $true + + # Add the Certificate Password to the Credentials List + Save-Credentials -UserName "certificatepassword" + } + "ApplicationWithSecret" { Add-ConfigurationDataEntry -Node "NonNodeData" ` - -Key "CertificateThumbprint" ` - -Value $CertificateThumbprint ` - -Description "Thumbprint of the certificate to use for authentication" + -Key "ApplicationSecret" ` + -Value $ApplicationSecret ` + -Description "Azure AD Application Secret for Authentication" + } + "Credentials" + { + if ($newline) + { + $DSCContent.Append("`r`n") | Out-Null + } + $DSCContent.Append(" [parameter()]`r`n") | Out-Null + $DSCContent.Append(" [System.Management.Automation.PSCredential]`r`n") | Out-Null + $DSCContent.Append(" `$Credential`r`n") | Out-Null + + if ($newline) + { + $postParamContent.Append("`r`n") | Out-Null + } + $postParamContent.Append(" if (`$null -eq `$Credential)`r`n") | Out-Null + $postParamContent.Append(" {`r`n") | Out-Null + $postParamContent.Append(" <# Credentials #>`r`n") | Out-Null + $postParamContent.Append(" }`r`n") | Out-Null + $postParamContent.Append(" else`r`n") | Out-Null + $postParamContent.Append(" {`r`n") | Out-Null + $postParamContent.Append(" `$CredsCredential = `$Credential`r`n") | Out-Null + $postParamContent.Append(" }`r`n`r`n") | Out-Null + $postParamContent.Append(" `$OrganizationName = `$CredsCredential.UserName.Split('@')[1]`r`n") | Out-Null + + $newline = $true + + # Add the Credential to the Credentials List + Save-Credentials -UserName "credential" } } - [array]$ModuleVersion = Get-Module Microsoft365DSC - $ModuleVersion = $ModuleVersion[0] + $DSCContent.Append(" )`r`n`r`n") | Out-Null + $DSCContent.Append($postParamContent.ToString()) | Out-Null + $DSCContent.Append("`r`n") | Out-Null + + # Create Node section $DSCContent.Append(" Import-DscResource -ModuleName 'Microsoft365DSC' -ModuleVersion '$version'`r`n`r`n") | Out-Null $DSCContent.Append(" Node localhost`r`n") | Out-Null $DSCContent.Append(" {`r`n") | Out-Null @@ -403,14 +397,33 @@ function Start-M365DSCConfigurationExtract -Value "0" ` -Description "Default Value Used to Ensure a Configuration Data File is Generated" - if ($ConnectionMode -eq 'Credentials') - { - # Add the Credential to the Credentials List - Save-Credentials -UserName "credential" - } - else + $ResourcesPath = Join-Path -Path $PSScriptRoot ` + -ChildPath "..\DSCResources\" ` + -Resolve + $AllResources = Get-ChildItem $ResourcesPath -Recurse | Where-Object { $_.Name -like 'MSFT_*.psm1' } + + $i = 1 + $ResourcesToExport = @() + $ResourcesPath = @() + foreach ($ResourceModule in $AllResources) { - Save-Credentials -UserName "certificatepassword" + try + { + $resourceName = $ResourceModule.Name.Split('.')[0] -replace 'MSFT_', '' + + if ((($Components -and ($Components -contains $resourceName)) -or $AllComponents -or ` + (-not $Components -and $null -eq $Workloads)) -and ` + ($ComponentsSpecified -or ($ComponentsToSkip -notcontains $resourceName)) -and ` + $resourcesNotSupported -notcontains $resourceName) + { + $ResourcesToExport += $ResourceName + $ResourcesPath += $ResourceModule + } + } + catch + { + New-M365DSCLogEntry -Error $_ -Message $ResourceModule.Name -Source "[M365DSCReverse]$($ResourceModule.Name)" + } } # Retrieve the list of Workloads represented by the resources to export and pre-authenticate to each one; @@ -445,54 +458,53 @@ function Start-M365DSCConfigurationExtract foreach ($resource in $ResourcesPath) { + $resourceName = $resource.Name.Split('.')[0] -replace 'MSFT_', '' + $mostSecureAuthMethod = ($allSupportedResourcesWithMostSecureAuthMethod | Where-Object { $_.Resource -eq $resourceName }).AuthMethod + Import-Module $resource.FullName | Out-Null $MaxProcessesExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("MaxProcesses") - $AppSecretExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("ApplicationSecret") - $CertThumbprintExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("CertificateThumbprint") - $TenantIdExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("TenantId") - $AppIdExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("ApplicationId") - $AppSecretExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("ApplicationSecret") - $CredentialExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("Credential") - $CertPathExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("CertificatePath") - $CertPasswordExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("CertificatePassword") $FilterExists = (Get-Command 'Export-TargetResource').Parameters.Keys.Contains("Filter") $parameters = @{} - if ($CredentialExists -and -not [System.String]::IsNullOrEmpty($Credential)) + switch ($mostSecureAuthMethod) { - $parameters.Add("Credential", $Credential) + { $_ -in "CertificateThumbprint", "CertificatePath", "ApplicationSecret" } + { + $parameters.Add("ApplicationId", $ApplicationId) + $parameters.Add("TenantId", $TenantId) + } + "CertificateThumbprint" + { + $parameters.Add("CertificateThumbprint", $CertificateThumbprint) + } + "CertificatePath" + { + $parameters.Add("CertificatePath", $CertificatePath) + $parameters.Add("CertificatePassword", $CertificatePassword) + } + "ApplicationSecret" + { + $parameters.Add("ApplicationSecret", $ApplicationSecret) + } + "Credentials" + { + $parameters.Add("Credential", $Credential) + } } + if ($MaxProcessesExists -and -not [System.String]::IsNullOrEmpty($MaxProcesses)) { $parameters.Add("MaxProcesses", $MaxProcesses) } - if ($CertThumbprintExists -and -not [System.String]::IsNullOrEmpty($CertificateThumbprint)) - { - $parameters.Add("CertificateThumbprint", $CertificateThumbprint) - } - if ($TenantIdExists -and -not [System.String]::IsNullOrEmpty($TenantId)) - { - $parameters.Add("TenantId", $TenantId) - } - if ($AppIdExists -and -not [System.String]::IsNullOrEmpty($ApplicationId)) - { - $parameters.Add("ApplicationId", $ApplicationId) - } - if ($AppSecretExists -and -not [System.String]::IsNullOrEmpty($ApplicationSecret)) - { - $parameters.Add("ApplicationSecret", $ApplicationSecret) - } - if ($CertPathExists -and -not [System.String]::IsNullOrEmpty($CertificatePath)) - { - $parameters.Add("CertificatePath", $CertificatePath) - } - if ($CertPasswordExists -and $null -ne $CertificatePassword) + + if ($FilterExists -and -not [System.String]::IsNullOrEmpty($Filter)) { - $parameters.Add("CertificatePassword", $CertificatePassword) + $parameters.Add("Filter", $Filter) } - if ($ComponentsToSkip -notcontains $resource.Name.Split('.')[0] -replace 'MSFT_', '') + + if ($ComponentsToSkip -notcontains $resourceName) { - Write-Host "[$i/$($ResourcesToExport.Length)] Extracting [$($resource.Name.Split('.')[0] -replace 'MSFT_', '')]..." -NoNewline + Write-Host "[$i/$($ResourcesToExport.Length)] Extracting [$resourceName]..." -NoNewline $exportString = [System.Text.StringBuilder]::New() if ($GenerateInfo) { @@ -526,34 +538,10 @@ function Start-M365DSCConfigurationExtract $DSCContent.Append(" }`r`n") | Out-Null $DSCContent.Append("}`r`n") | Out-Null - if ($ConnectionMode -eq 'Credentials') + $launchCommand = "$ConfigurationName -ConfigurationData .\ConfigurationData.psd1" + switch ($AuthMethods) { - #region Add the Prompt for Required Credentials at the top of the Configuration - $credsContent = "" - foreach ($credEntry in $Global:CredsRepo) - { - if (!$credEntry.ToLower().StartsWith("builtin")) - { - if (!$AzureAutomation) - { - $credsContent += " " + (Resolve-Credentials $credEntry) + " = Get-Credential -Message `"Credentials`"`r`n" - } - else - { - $resolvedName = (Resolve-Credentials $credEntry) - $credsContent += " " + $resolvedName + " = Get-AutomationPSCredential -Name " + ($resolvedName.Replace("$", "")) + "`r`n" - } - } - } - $credsContent += "`r`n" - $startPosition = $DSCContent.ToString().IndexOf("<# Credentials #>") + 19 - $DSCContent = $DSCContent.Insert($startPosition, $credsContent) - $DSCContent.Append("$ConfigurationName -ConfigurationData .\ConfigurationData.psd1 -Credential `$Credential") | Out-Null - #endregion - } - else - { - if (-not [System.String]::IsNullOrEmpty($CertificatePassword)) + "CertificatePath" { $certCreds = $Global:CredsRepo[0] $credsContent = "" @@ -561,14 +549,38 @@ function Start-M365DSCConfigurationExtract $credsContent += "`r`n" $startPosition = $DSCContent.IndexOf("<# Credentials #>") + 19 $DSCContent = $DSCContent.Insert($startPosition, $credsContent) - $DSCContent.Append("$ConfigurationName -ConfigurationData .\ConfigurationData.psd1 -CertificatePassword `$CertificatePassword") | Out-Null + $launchCommand += " -CertificatePassword `$CertificatePassword" } - else + "Credentials" { - $DSCContent.Append("$ConfigurationName -ConfigurationData .\ConfigurationData.psd1") | Out-Null + #region Add the Prompt for Required Credentials at the top of the Configuration + $credsContent = "" + foreach ($credEntry in $Global:CredsRepo) + { + if (!$credEntry.ToLower().StartsWith("builtin")) + { + if (!$AzureAutomation) + { + $credsContent += " " + (Resolve-Credentials $credEntry) + " = Get-Credential -Message `"Credentials`"`r`n" + } + else + { + $resolvedName = (Resolve-Credentials $credEntry) + $credsContent += " " + $resolvedName + " = Get-AutomationPSCredential -Name " + ($resolvedName.Replace("$", "")) + "`r`n" + } + } + } + $credsContent += "`r`n" + $startPosition = $DSCContent.ToString().IndexOf("<# Credentials #>") + 19 + $DSCContent = $DSCContent.Insert($startPosition, $credsContent) + $launchCommand += " -Credential `$Credential" + #endregion } } + $DSCContent.Append("`r`n") | Out-Null + $DSCContent.Append($launchCommand) | Out-Null + #region Benchmarks $M365DSCExportEndTime = [System.DateTime]::Now $timeTaken = New-TimeSpan -Start ($M365DSCExportStartTime.ToString()) ` @@ -673,29 +685,38 @@ function Start-M365DSCConfigurationExtract if (!$AzureAutomation) { - $LCMConfig = Get-DscLocalConfigurationManager - if ($null -ne $LCMConfig.CertificateID) + if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - try - { - # Export the certificate assigned to the LCM - $certPath = $OutputDSCPath + "M365DSC.cer" - Export-Certificate -FilePath $certPath ` - -Cert "cert:\LocalMachine\my\$($LCMConfig.CertificateID)" ` - -Type CERT ` - -NoClobber | Out-Null - } - catch + $LCMConfig = Get-DscLocalConfigurationManager + if ($null -ne $LCMConfig.CertificateID) { - Write-Verbose -Message $_ - Add-M365DSCEvent -Message $_ -EntryType 'Error' ` - -EventID 1 -Source $($MyInvocation.MyCommand.Source) - } + try + { + # Export the certificate assigned to the LCM + $certPath = $OutputDSCPath + "M365DSC.cer" + Export-Certificate -FilePath $certPath ` + -Cert "cert:\LocalMachine\my\$($LCMConfig.CertificateID)" ` + -Type CERT ` + -NoClobber | Out-Null + } + catch + { + Write-Verbose -Message $_ + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) + } - Add-ConfigurationDataEntry -Node "localhost" ` - -Key "CertificateFile" ` - -Value "M365DSC.cer" ` - -Description "Path of the certificate used to encrypt credentials in the file." + Add-ConfigurationDataEntry -Node "localhost" ` + -Key "CertificateFile" ` + -Value "M365DSC.cer" ` + -Description "Path of the certificate used to encrypt credentials in the file." + } + } + else + { + Write-Host "$($Global:M365DSCEmojiYellowCircle) Warning {" -NoNewline + Write-Host "Cannot export Local Configuration Manager settings. This process isn't executed with Administrative Privileges!" -NoNewline -ForegroundColor DarkCyan + Write-Host "}" } $outputConfigurationData = $OutputDSCPath + "ConfigurationData.psd1" New-ConfigurationDataDocument -Path $outputConfigurationData @@ -743,6 +764,8 @@ function Get-M365DSCResourcesByWorkloads $Mode = 'Default' ) + Write-Host "Finding all resources for workload {$Workload} and Mode {$Mode}" -ForegroundColor Gray + $modules = Get-ChildItem -Path ($PSScriptRoot + "\..\DSCResources\") -Recurse -Filter '*.psm1' $Components = @() foreach ($resource in $modules) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index 6890843f95..0b2f857f56 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -193,7 +193,7 @@ function Convert-M365DscArrayToString if ($item -is [System.Collections.Hashtable]) { $str += "{" - $str += Convert-M365DSCHashtableToString -Hashtable $item + $str += Convert-M365DscHashtableToString -Hashtable $item $str += "}" } elseif ($Array[$i] -is [Microsoft.Management.Infrastructure.CimInstance]) @@ -1041,12 +1041,77 @@ function Export-M365DSCConfiguration [System.String] $CertificatePath ) + + # LaunchWebUI specified, launching that now + if ($LaunchWebUI) + { + Write-Output -InputObject "Launching web page 'https://export.microsoft365dsc.com'" + explorer "https://export.microsoft365dsc.com" + return + } + # Suppress Progress overlays $Global:ProgressPreference = 'SilentlyContinue' # Suppress Warnings $Global:WarningPreference = 'SilentlyContinue' + ##### FIRST CHECK AUTH PARAMETERS + if ($PSBoundParameters.ContainsKey("CertificatePath") -eq $true -and ` + $PSBoundParameters.ContainsKey("CertificatePassword") -eq $false) + { + Write-Host -Object "[ERROR] You have to specify CertificatePassword when you specify CertificatePath" -ForegroundColor Red + return + } + + if ($PSBoundParameters.ContainsKey("CertificatePassword") -eq $true -and ` + $PSBoundParameters.ContainsKey("CertificatePath") -eq $false) + { + Write-Host -Object "[ERROR] You have to specify CertificatePath when you specify CertificatePassword" -ForegroundColor Red + return + } + + if ($PSBoundParameters.ContainsKey("ApplicationId") -eq $true -and ` + $PSBoundParameters.ContainsKey("TenantId") -eq $false) + { + Write-Host -Object "[ERROR] You have to specify TenantId when you specify ApplicationId" -ForegroundColor Red + return + } + + if ($PSBoundParameters.ContainsKey("ApplicationId") -eq $false -and ` + $PSBoundParameters.ContainsKey("TenantId") -eq $true) + { + Write-Host -Object "[ERROR] You have to specify ApplicationId when you specify TenantId" -ForegroundColor Red + return + } + + if ($PSBoundParameters.ContainsKey("ApplicationId") -eq $true -and ` + $PSBoundParameters.ContainsKey("TenantId") -eq $true -and ` + ($PSBoundParameters.ContainsKey("CertificateThumbprint") -eq $false -and ` + $PSBoundParameters.ContainsKey("ApplicationSecret") -eq $false -and ` + $PSBoundParameters.ContainsKey("CertificatePath") -eq $false)) + { + Write-Host -Object "[ERROR] You have to specify ApplicationSecret, CertificateThumbprint or CertificatePath when you specify ApplicationId/TenantId" -ForegroundColor Red + return + } + + if (($PSBoundParameters.ContainsKey("ApplicationId") -eq $false -or ` + $PSBoundParameters.ContainsKey("TenantId") -eq $false) -and ` + ($PSBoundParameters.ContainsKey("CertificateThumbprint") -eq $true -or ` + $PSBoundParameters.ContainsKey("ApplicationSecret") -eq $true -or ` + $PSBoundParameters.ContainsKey("CertificatePath") -eq $true)) + { + Write-Host -Message "[ERROR] You have to specify ApplicationId and TenantId when you specify ApplicationSecret, CertificateThumbprint or CertificatePath" -ForegroundColor Red + return + } + + # Default to Credential if no authentication mechanism were provided + if ($PSBoundParameters.ContainsKey("Credential") -eq $false -and ` + $PSBoundParameters.ContainsKey("ApplicationId") -eq $false) + { + $Credential = Get-Credential + } + #region Telemetry $data = [System.Collections.Generic.Dictionary[[String], [String]]]::new() $data.Add("Event", "Extraction") @@ -1087,90 +1152,79 @@ function Export-M365DSCConfiguration # Make sure we are not connected to Microsoft Graph on another tenant try { - Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null - } - catch - { - Write-Verbose -Message "No existing connections to Microsoft Graph" - } - - # Default to Credential if no authentication mechanism were provided - if (-not $Credential -and (-not $ApplicationId -or -not $TenantId -or (-not $ApplicationSecret -and -not $CertificateThumbprint)) -and -not $LaunchWebUI) - { - $Credential = Get-Credential + $global:MsCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false } +} +catch +{ +} - if (-not [System.String]::IsNullOrEmpty($TenantId)) - { - $data.Add("Tenant", $TenantId) - } - else +if (-not [System.String]::IsNullOrEmpty($TenantId)) +{ + $data.Add("Tenant", $TenantId) +} +else +{ + if ($Credential) { - if ($Credential) - { - $tenant = $Credential.UserName.Split('@')[1] - $data.Add("Tenant", $tenant) - } + $tenant = $Credential.UserName.Split('@')[1] + $data.Add("Tenant", $tenant) } +} - Add-M365DSCTelemetryEvent -Data $data - if ($LaunchWebUI) - { - explorer "https://export.microsoft365dsc.com" - } - else - { - if ($null -ne $Workloads) - { - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Workloads $Workloads ` - -Mode $Mode ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo ` - -Filters $Filters - } - elseif ($null -ne $Components) - { - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Components $Components ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo ` - -Filters $Filters - } - elseif ($null -ne $Mode) - { - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Mode $Mode ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo ` - -AllComponents ` - -Filters $Filters - } - } +Add-M365DSCTelemetryEvent -Data $data +if ($null -ne $Workloads) +{ + Write-Output -InputObject "Exporting Microsoft 365 configuration for Workloads: $($Workloads -join ", ")" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Workloads $Workloads ` + -Mode $Mode ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo +} +elseif ($null -ne $Components) +{ + Write-Output -InputObject "Exporting Microsoft 365 configuration for Components: $($Components -join ", ")" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Components $Components ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo ` + -Filters $Filters +} +elseif ($null -ne $Mode) +{ + Write-Output -InputObject "Exporting Microsoft 365 configuration for Mode: $Mode" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Mode $Mode ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo ` + -AllComponents ` + -Filters $Filters +} } $Script:M365DSCDependenciesValidated = $false @@ -1208,11 +1262,6 @@ function Confirm-M365DSCDependencies else { Write-Verbose -Message "Dependencies were all successfully validated." - - #region Remove invalid versions of depencendies from session - Remove-M365DSCInvalidDependenciesFromSession - #endregion - $Script:M365DSCDependenciesValidated = $true } } @@ -1243,51 +1292,7 @@ function Import-M365DSCDependencies foreach ($dependency in $dependencies) { - Import-Module $dependency.ModuleName -RequiredVersion $dependency.RequiredVersion -Force - } -} - -<# -.Description -This function removes all versions of dependencies that are not specified in the manifest from the current PowerShell session. - -.Example -Remove-M365DSCInvalidDependenciesFromSession - -.Functionality -Private -#> -function Remove-M365DSCInvalidDependenciesFromSession -{ - [CmdletBinding()] - param() - - $currentPath = Join-Path -Path $PSScriptRoot -ChildPath '..\' -Resolve - $manifest = Import-PowerShellDataFile "$currentPath/Dependencies/Manifest.psd1" - $dependencies = $manifest.Dependencies - - foreach ($dependency in $dependencies) - { - $loadedModuleInstances = Get-Module $dependency.ModuleName - - $incorrectModuleVersions = $null - if ($loadedModuleInstances) - { - $incorrectModuleVersions = $loadedModuleInstances | Where-Object -FilterScript {$_.Version -ne $dependency.RequiredVersion} - - if ($incorrectModuleVersions) - { - foreach ($incorrectVersion in $incorrectModuleVersions) - { - $FQN = @{ - ModuleName = $incorrectVersion.Name - ModuleVersion = $incorrectVersion.Version - } - Write-Verbose -Message "Removing Module {$($incorrectVersion.Name)} version {$($incorrectVersion.Version)} from the current PowerShell session" - Remove-Module -FullyQualifiedName $FQN -Force -ErrorAction SilentlyContinue - } - } - } + Import-Module $dependency.ModuleName -Force } } @@ -2357,12 +2362,6 @@ function Assert-M365DSCBlueprint $ResourcesInBluePrint += $resource.ResourceName } } - - if (!$ResourcesInBluePrint) { - Write-Host "Malformed BluePrint, aborting" - break - } - Write-Host "Selected BluePrint contains ($($ResourcesInBluePrint.Length)) components to assess." # Call the Export-M365DSCConfiguration cmdlet to extract only the resource @@ -3069,6 +3068,81 @@ function Get-M365DSCComponentsForAuthenticationType return $Components } +<# +.Description +This function gets all resources that support the specified authentication method and +determines the most secure authentication method supported by the resource. + +.Functionality +Internal +#> +function Get-M365DSCComponentsWithMostSecureAuthenticationType +{ + [CmdletBinding()] + [OutputType([System.String[]])] + param( + [Parameter()] + [System.String[]] + [ValidateSet('ApplicationWithSecret', 'CertificateThumbprint', 'CertificatePath', 'Credentials')] + $AuthenticationMethod + ) + + $modules = Get-ChildItem -Path ($PSScriptRoot + "\..\DSCResources\") -Recurse -Filter '*.psm1' + $Components = @() + foreach ($resource in $modules) + { + Import-Module $resource.FullName -Force + $parameters = (Get-Command 'Set-TargetResource').Parameters.Keys + + #Case - Resource supports CertificateThumbprint + if ($AuthenticationMethod.Contains("CertificateThumbprint") -and ` + $parameters.Contains('ApplicationId') -and ` + $parameters.Contains('CertificateThumbprint') -and ` + $parameters.Contains('TenantId')) + { + $Components += @{ + Resource = $resource.Name -replace "MSFT_", "" -replace ".psm1", "" + AuthMethod = "CertificateThumbprint" + } + } + + # Case - Resource supports CertificatePath + elseif ($AuthenticationMethod.Contains("CertificatePath") -and ` + $parameters.Contains('ApplicationId') -and ` + $parameters.Contains('CertificatePath') -and ` + $parameters.Contains('TenantId')) + { + $Components += @{ + Resource = $resource.Name -replace "MSFT_", "" -replace ".psm1", "" + AuthMethod = "CertificatePath" + } + } + + # Case - Resource supports ApplicationSecret + elseif ($AuthenticationMethod.Contains("ApplicationWithSecret") -and ` + $parameters.Contains('ApplicationId') -and ` + $parameters.Contains('ApplicationSecret') -and ` + $parameters.Contains('TenantId')) + { + $Components += @{ + Resource = $resource.Name -replace "MSFT_", "" -replace ".psm1", "" + AuthMethod = "ApplicationSecret" + } + } + + # Case - Resource supports Credential + elseif ($AuthenticationMethod.Contains("Credentials") -and ` + $parameters.Contains('Credential')) + { + $Components += @{ + Resource = $resource.Name -replace "MSFT_", "" -replace ".psm1", "" + AuthMethod = "Credentials" + } + } + } + return $Components +} + <# .Description This function gets all available M365DSC resources in the module @@ -3514,6 +3588,65 @@ $resourceExample return $exampleText } +<# +.Description + } + + if ($params.ContainsKey("ApplicationSecret")) + { + $params.Remove("ApplicationSecret") + } + + if ($params.ContainsKey("CertificateThumbprint")) + { + $params.Remove("CertificateThumbprint") + } + + if ($params.ContainsKey("CertificatePath")) + { + $params.Remove("CertificatePath") + } + + if ($params.ContainsKey("CertificatePassword")) + { + $params.Remove("CertificatePassword") + } + + [string]$userName = 'admin@contoso.onmicrosoft.com' + [string]$userPassword = 'dummypassword' + [securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force + [pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword) + + $resourceExample = Get-M365DSCExportContentForResource -ResourceName $ResourceName -ModulePath $resource.Path -Results $params -ConnectionMode Credentials -Credential $credObject + + $resourceExample = $resourceExample.TrimEnd() -replace ";", "" + + $exampleText = @" +<# +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(Mandatory = `$true)] + [PSCredential] + `$credsGlobalAdmin + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { +$resourceExample + } +} +"@ + + return $exampleText +} + <# .Description This function creates an example from the resource schema, using ReverseDSC code. @@ -3609,6 +3742,7 @@ Export-ModuleMember -Function @( 'Get-M365DSCAllResources', 'Get-M365DSCAuthenticationMode', 'Get-M365DSCComponentsForAuthenticationType', + 'Get-M365DSCComponentsWithMostSecureAuthenticationType', 'Get-M365DSCExportContentForResource', 'Get-M365DSCOrganization', 'Get-M365DSCTenantDomain', From 76a3a74097e146293b819aa8e86f47c4c615cf87 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Fri, 26 Aug 2022 13:56:05 +0200 Subject: [PATCH 05/10] Fixing rebase issues --- .../MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 | 105 +++++----- .../Modules/M365DSCReverse.psm1 | 9 +- .../Microsoft365DSC/Modules/M365DSCUtil.psm1 | 196 ++++++------------ 3 files changed, 130 insertions(+), 180 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 index ced0c6a076..306c675413 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 @@ -314,28 +314,68 @@ function Export-TargetResource Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.Id)}" try { - [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id -ErrorAction 'SilentlyContinue' + [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id ` + -All:$true ` + -Filter $Filter ` + -ErrorAction 'SilentlyContinue' $j = 1 foreach ($plan in $plans) + { + $params = @{ + Title = $plan.Title + OwnerGroup = $group.ObjectId + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + } + + Write-Host " [$j/$($plans.Length)] $($plan.Title)" + $results = Get-TargetResource @params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + $dscContent += $currentDSCBlock + + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $j++ + } + $i++ + } + catch + { + try + { + Write-Verbose -Message $_ + $tenantIdValue = "" + if (-not [System.String]::IsNullOrEmpty($TenantId)) + { + $tenantIdValue = $TenantId + } + elseif ($null -ne $Credential) + { + $tenantIdValue = $Credential.UserName.Split('@')[1] + } + Add-M365DSCEvent -Message $_ -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + } + catch + { + Write-Verbose -Message $_ + } + Write-Host " |---[$j/$($plans.Length)] $($plle)" Credential = $Credential } - $results = Get-TargetResource @params - $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` - -Results $Results - $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` - -ConnectionMode $ConnectionMode ` - -ModulePath $PSScriptRoot ` - -Results $Results ` - -Credential $Credential - $dscContent += $currentDSCBlock - - Save-M365DSCPartialExport -Content $currentDSCBlock ` - -FileName $Global:PartialExportFileName - $j++ } - $i++ + + return $dscContent } catch { @@ -361,40 +401,5 @@ function Export-TargetResource } } } -return $dscContent -} -catch -{ - try - { - Write-Verbose -Message $_ - $tenantIdValue = "" - if (-not [System.String]::IsNullOrEmpty($TenantId)) - { - $tenantIdValue = $TenantId - } - elseif ($null -ne $Credential) - { - $tenantIdValue = $Credential.UserName.Split('@')[1] - } - Add-M365DSCEvent -Message $_ -EntryType 'Error' ` - -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` - -TenantId $tenantIdValue - } - catch - { - Write-Verbose -Message $_ - } - return "" -} -} - -Export-ModuleMember -Function *-TargetResource - { - Write-Verbose -Message $_ - } - return "" - } -} Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 index 90e3475d36..e1d94c2d5c 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCReverse.psm1 @@ -129,24 +129,31 @@ function Start-M365DSCConfigurationExtract # we are allowed to export the selected components. $AuthMethods = @() + Write-Host -Object " " + Write-Host -Object "Authentication methods specified:" if ($null -ne $Credential) { + Write-Host -Object "- Credentials" $AuthMethods += "Credentials" } if (-not [System.String]::IsNullOrEmpty($CertificateThumbprint)) { + Write-Host -Object "- Service Principal with Certificate Thumbprint" $AuthMethods += "CertificateThumbprint" } if (-not [System.String]::IsNullOrEmpty($CertificatePath)) { + Write-Host -Object "- Service Principal with Certificate Path" $AuthMethods += "CertificatePath" } if (-not [System.String]::IsNullOrEmpty($ApplicationSecret)) { + Write-Host -Object "- Service Principal with Application Secret" $AuthMethods += "ApplicationWithSecret" } + Write-Host -Object " " $allSupportedResourcesWithMostSecureAuthMethod = Get-M365DSCComponentsWithMostSecureAuthenticationType -AuthenticationMethod $AuthMethods @@ -504,7 +511,7 @@ function Start-M365DSCConfigurationExtract if ($ComponentsToSkip -notcontains $resourceName) { - Write-Host "[$i/$($ResourcesToExport.Length)] Extracting [$resourceName]..." -NoNewline + Write-Host "[$i/$($ResourcesToExport.Length)] Extracting [$resourceName] using {$mostSecureAuthMethod}..." -NoNewline $exportString = [System.Text.StringBuilder]::New() if ($GenerateInfo) { diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index 0b2f857f56..d5142fe10f 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -1154,77 +1154,76 @@ function Export-M365DSCConfiguration { $global:MsCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false } -} -catch -{ -} + catch + { + } -if (-not [System.String]::IsNullOrEmpty($TenantId)) -{ - $data.Add("Tenant", $TenantId) -} -else -{ - if ($Credential) + if (-not [System.String]::IsNullOrEmpty($TenantId)) { - $tenant = $Credential.UserName.Split('@')[1] - $data.Add("Tenant", $tenant) + $data.Add("Tenant", $TenantId) + } + else + { + if ($Credential) + { + $tenant = $Credential.UserName.Split('@')[1] + $data.Add("Tenant", $tenant) + } } -} -Add-M365DSCTelemetryEvent -Data $data -if ($null -ne $Workloads) -{ - Write-Output -InputObject "Exporting Microsoft 365 configuration for Workloads: $($Workloads -join ", ")" - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Workloads $Workloads ` - -Mode $Mode ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo -} -elseif ($null -ne $Components) -{ - Write-Output -InputObject "Exporting Microsoft 365 configuration for Components: $($Components -join ", ")" - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Components $Components ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo ` - -Filters $Filters -} -elseif ($null -ne $Mode) -{ - Write-Output -InputObject "Exporting Microsoft 365 configuration for Mode: $Mode" - Start-M365DSCConfigurationExtract -Credential $Credential ` - -Mode $Mode ` - -Path $Path -FileName $FileName ` - -MaxProcesses $MaxProcesses ` - -ConfigurationName $ConfigurationName ` - -ApplicationId $ApplicationId ` - -ApplicationSecret $ApplicationSecret ` - -TenantId $TenantId ` - -CertificateThumbprint $CertificateThumbprint ` - -CertificatePath $CertificatePath ` - -CertificatePassword $CertificatePassword ` - -GenerateInfo $GenerateInfo ` - -AllComponents ` - -Filters $Filters -} + Add-M365DSCTelemetryEvent -Data $data + if ($null -ne $Workloads) + { + Write-Output -InputObject "Exporting Microsoft 365 configuration for Workloads: $($Workloads -join ", ")" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Workloads $Workloads ` + -Mode $Mode ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo + } + elseif ($null -ne $Components) + { + Write-Output -InputObject "Exporting Microsoft 365 configuration for Components: $($Components -join ", ")" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Components $Components ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo ` + -Filters $Filters + } + elseif ($null -ne $Mode) + { + Write-Output -InputObject "Exporting Microsoft 365 configuration for Mode: $Mode" + Start-M365DSCConfigurationExtract -Credential $Credential ` + -Mode $Mode ` + -Path $Path -FileName $FileName ` + -MaxProcesses $MaxProcesses ` + -ConfigurationName $ConfigurationName ` + -ApplicationId $ApplicationId ` + -ApplicationSecret $ApplicationSecret ` + -TenantId $TenantId ` + -CertificateThumbprint $CertificateThumbprint ` + -CertificatePath $CertificatePath ` + -CertificatePassword $CertificatePassword ` + -GenerateInfo $GenerateInfo ` + -AllComponents ` + -Filters $Filters + } } $Script:M365DSCDependenciesValidated = $false @@ -3588,65 +3587,6 @@ $resourceExample return $exampleText } -<# -.Description - } - - if ($params.ContainsKey("ApplicationSecret")) - { - $params.Remove("ApplicationSecret") - } - - if ($params.ContainsKey("CertificateThumbprint")) - { - $params.Remove("CertificateThumbprint") - } - - if ($params.ContainsKey("CertificatePath")) - { - $params.Remove("CertificatePath") - } - - if ($params.ContainsKey("CertificatePassword")) - { - $params.Remove("CertificatePassword") - } - - [string]$userName = 'admin@contoso.onmicrosoft.com' - [string]$userPassword = 'dummypassword' - [securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force - [pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword) - - $resourceExample = Get-M365DSCExportContentForResource -ResourceName $ResourceName -ModulePath $resource.Path -Results $params -ConnectionMode Credentials -Credential $credObject - - $resourceExample = $resourceExample.TrimEnd() -replace ";", "" - - $exampleText = @" -<# -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(Mandatory = `$true)] - [PSCredential] - `$credsGlobalAdmin - ) - Import-DscResource -ModuleName Microsoft365DSC - - node localhost - { -$resourceExample - } -} -"@ - - return $exampleText -} - <# .Description This function creates an example from the resource schema, using ReverseDSC code. @@ -3694,9 +3634,6 @@ function New-M365DSCMissingResourcesExample } } - - - <# .Description This function validates there are no updates to the module or it's dependencies and no multiple versions are present on the local system. @@ -3731,6 +3668,7 @@ function Test-M365DSCModuleValidity Write-Host "Update-Module -Name 'Microsoft365DSC' -Force`nUpdate-M365DSCDependencies -Force`nUninstall-M365DSCOutdatedDependencies" -ForegroundColor Blue } } + Export-ModuleMember -Function @( 'Assert-M365DSCBlueprint', 'Confirm-ImportedCmdletIsAvailable', From b89f91f0ee333c3341ef85f520d168f7bc75cc9d Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Fri, 26 Aug 2022 14:10:19 +0200 Subject: [PATCH 06/10] Corrected issue --- Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index d5142fe10f..83489083e2 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -1152,10 +1152,12 @@ function Export-M365DSCConfiguration # Make sure we are not connected to Microsoft Graph on another tenant try { + Disconnect-MgGraph -ErrorAction Stop | Out-Null $global:MsCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false } catch { + Write-Verbose -Message "No existing connections to Microsoft Graph" } if (-not [System.String]::IsNullOrEmpty($TenantId)) From a0be6df9b7797f70ddeddbf4a2a0d57362ae0ddf Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Fri, 26 Aug 2022 14:40:15 +0200 Subject: [PATCH 07/10] Removed unnecessary comments --- .../DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 index 8e97e492e0..7239b6ddad 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerTask/MSFT_PlannerTask.psm1 @@ -562,8 +562,6 @@ function Export-TargetResource try { [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id -ErrorAction 'SilentlyContinue' - # [Array]$plans = Get-M365DSCPlannerPlansFromGroup -GroupId $group.Id ` - # -Credential $Credential $j = 1 foreach ($plan in $plans) @@ -571,8 +569,7 @@ function Export-TargetResource Write-Host " |---[$j/$($plans.Length)] $($plan.Title)" [Array]$tasks = Get-MgGroupPlannerPlanTask -GroupId $group.Id -PlannerPlanId $plan.Id -ErrorAction 'SilentlyContinue' - # [Array]$tasks = Get-M365DSCPlannerTasksFromPlan -PlanId ` - # -Credential $Credential + $k = 1 foreach ($task in $tasks) { From 1c07e6e486247b581d33bd8d2c9405e6ed03e337 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Mon, 29 Aug 2022 15:19:23 +0200 Subject: [PATCH 08/10] Fixing failing unit tests --- .../MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 | 13 +- .../Microsoft365DSC.PlannerBucket.Tests.ps1 | 80 +++--- .../Microsoft365DSC.PlannerPlan.Tests.ps1 | 78 +++--- .../Microsoft365DSC.PlannerTask.Tests.ps1 | 244 +++++++++--------- Tests/Unit/Stubs/Generic.psm1 | 18 +- 5 files changed, 202 insertions(+), 231 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 index 306c675413..9eac58b802 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 @@ -299,7 +299,8 @@ function Export-TargetResource Add-M365DSCTelemetryEvent -Data $data #endregion - $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + $ConnectionMode = "Credentials" + $null = New-M365DSCConnection -Workload 'MicrosoftGraph' ` -InboundParameters $PSBoundParameters try @@ -314,7 +315,7 @@ function Export-TargetResource Write-Host " [$i/$($groups.Length)] $($group.DisplayName) - {$($group.Id)}" try { - [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id ` + [Array]$plans = Get-MgGroupPlannerPlan -GroupId $group.Id ` -All:$true ` -Filter $Filter ` -ErrorAction 'SilentlyContinue' @@ -323,11 +324,9 @@ function Export-TargetResource foreach ($plan in $plans) { $params = @{ - Title = $plan.Title - OwnerGroup = $group.ObjectId - ApplicationId = $ApplicationId - TenantId = $TenantId - CertificateThumbprint = $CertificateThumbprint + Title = $plan.Title + OwnerGroup = $group.Id + Credential = $Credential } Write-Host " [$j/$($plans.Length)] $($plan.Title)" diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 index 80877bfbb9..00bfd34982 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 @@ -2,14 +2,14 @@ param( ) $M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` - -ChildPath "..\..\Unit" ` - -Resolve + -ChildPath "..\..\Unit" ` + -Resolve $CmdletModule = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Microsoft365.psm1" ` - -Resolve) + -ChildPath "\Stubs\Microsoft365.psm1" ` + -Resolve) $GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Generic.psm1" ` - -Resolve) + -ChildPath "\Stubs\Generic.psm1" ` + -Resolve) Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` -ChildPath "\UnitTestHelper.psm1" ` -Resolve) @@ -42,12 +42,10 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "When the Bucket doesn't exist but it should" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Name = "Contoso Bucket" - Ensure = "Present" - ApplicationId = "1234567890" - TenantId = "1234567890" - CertificateThumbprint = "1234567890" + PlanId = "1234567890" + Name = "Contoso Bucket" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { @@ -72,19 +70,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Bucket exists and is NOT in the Desired State" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Name = "Contoso Bucket" - Ensure = "Present" - ApplicationId = "1234567890" - TenantId = "1234567890" - CertificateThumbprint = "1234567890" + PlanId = "1234567890" + Name = "Contoso Bucket" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - PlanId = "1234567890" - Id = "12345" - Name = "Contoso Bucket" + PlanId = "1234567890" + Id = "12345" + Name = "Contoso Bucket" } } } @@ -97,19 +93,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task exists and is IN the Desired State" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Name = "Contoso Bucket" - Ensure = "Present" - ApplicationId = "1234567890" - TenantId = "1234567890" - CertificateThumbprint = "1234567890" + PlanId = "1234567890" + Name = "Contoso Bucket" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - PlanId = "1234567890" - Id = "12345" - Name = "Contoso Bucket" + PlanId = "1234567890" + Id = "12345" + Name = "Contoso Bucket" } } } @@ -126,19 +120,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task exists but it should not" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Name = "Contoso Bucket" - Ensure = "Absent" - ApplicationId = "1234567890" - TenantId = "1234567890" - CertificateThumbprint = "1234567890" + PlanId = "1234567890" + Name = "Contoso Bucket" + Ensure = "Absent" + Credential = $Credential } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - PlanId = "1234567890" - Id = "12345" - Name = "Contoso Bucket" + PlanId = "1234567890" + Id = "12345" + Name = "Contoso Bucket" } } } @@ -156,16 +148,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { BeforeAll { $Global:CurrentModeIsExport = $true $testParams = @{ - ApplicationId = "1234567890" - TenantId = "1234567890" - CertificateThumbprint = "1234567890" + Credential = $Credential } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - PlanId = "1234567890" - Id = "12345" - Name = "Contoso Bucket" + PlanId = "1234567890" + Id = "12345" + Name = "Contoso Bucket" } } } diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 index 04ceb05989..77f5b04523 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 @@ -2,14 +2,14 @@ param( ) $M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` - -ChildPath "..\..\Unit" ` - -Resolve + -ChildPath "..\..\Unit" ` + -Resolve $CmdletModule = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Microsoft365.psm1" ` - -Resolve) + -ChildPath "\Stubs\Microsoft365.psm1" ` + -Resolve) $GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Generic.psm1" ` - -Resolve) + -ChildPath "\Stubs\Generic.psm1" ` + -Resolve) Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` -ChildPath "\UnitTestHelper.psm1" ` -Resolve) @@ -23,7 +23,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { BeforeAll { $secpasswd = ConvertTo-SecureString "Pass@word1" -AsPlainText -Force - $Credential = New-Object System.Management.Automation.PSCredential ("tenantadmin", $secpasswd) + $Credential = New-Object System.Management.Automation.PSCredential ("tenantadmin@contoso.onmicrosoft.com", $secpasswd) Mock -CommandName Connect-Graph -MockWith { } @@ -42,19 +42,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "When the Plan doesn't exist but it should" -Fixture { BeforeAll { $testParams = @{ - Title = "Contoso Plan"; - OwnerGroup = "Contoso Group" - CertificateThumbprint = "12345678901234567890" - ApplicationId = "12345" - TenantId = "12345" - Ensure = 'Present' + Title = "Contoso Plan"; + OwnerGroup = "Contoso Group" + Credential = $Credential + Ensure = 'Present' } Mock -CommandName Get-MgGroup -MockWith { return @( @{ - DisplayName ="Contoso Group" - ObjectId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Group" + Id = "12345-12345-12345-12345-12345" } ) } @@ -81,19 +79,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Plan exists and is NOT in the Desired State" -Fixture { BeforeAll { $testParams = @{ - Title = "Contoso Plan"; - OwnerGroup = "Contoso Group" - CertificateThumbprint = "12345678901234567890" - ApplicationId = "12345" - TenantId = "12345" - Ensure = 'Present' + Title = "Contoso Plan"; + OwnerGroup = "Contoso Group" + Credential = $Credential + Ensure = 'Present' } Mock -CommandName Get-MgGroup -MockWith { return @( @{ - DisplayName ="Contoso Group" - ObjectId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Group" + Id = "12345-12345-12345-12345-12345" } ) } @@ -120,19 +116,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Plan exists and is IN the Desired State" -Fixture { BeforeAll { $testParams = @{ - Title = "Contoso Plan"; - OwnerGroup = "12345-12345-12345-12345-12345" - CertificateThumbprint = "12345678901234567890" - ApplicationId = "12345" - TenantId = "12345" - Ensure = 'Present' + Title = "Contoso Plan"; + OwnerGroup = "12345-12345-12345-12345-12345" + Credential = $Credential + Ensure = 'Present' } Mock -CommandName Get-MgGroup -MockWith { return @( @{ - DisplayName ="Contoso Group" - ObjectId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Group" + Id = "12345-12345-12345-12345-12345" } ) } @@ -157,19 +151,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Plan exists but it should not" -Fixture { BeforeAll { $testParams = @{ - Title = "Contoso Plan"; - OwnerGroup = "Contoso Group" - CertificateThumbprint = "12345678901234567890" - ApplicationId = "12345" - TenantId = "12345" - Ensure = 'Absent' + Title = "Contoso Plan"; + OwnerGroup = "Contoso Group" + Credential = $Credential + Ensure = 'Absent' } Mock -CommandName Get-MgGroup -MockWith { return @( @{ - DisplayName ="Contoso Group" - ObjectId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Group" + Id = "12345-12345-12345-12345-12345" } ) } @@ -196,16 +188,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { BeforeAll { $Global:CurrentModeIsExport = $true $testParams = @{ - CertificateThumbprint = "12345678901234567890" - ApplicationId = "12345" - TenantId = "12345" + Credential = $Credential } Mock -CommandName Get-MgGroup -MockWith { return @( @{ - DisplayName ="Contoso Group" - ObjectId = "12345-12345-12345-12345-12345" + DisplayName = "Contoso Group" + Id = "12345-12345-12345-12345-12345" } ) } diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 index 566139182c..e6b1e5fd44 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 @@ -2,14 +2,14 @@ param( ) $M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` - -ChildPath "..\..\Unit" ` - -Resolve + -ChildPath "..\..\Unit" ` + -Resolve $CmdletModule = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Microsoft365.psm1" ` - -Resolve) + -ChildPath "\Stubs\Microsoft365.psm1" ` + -Resolve) $GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath "\Stubs\Generic.psm1" ` - -Resolve) + -ChildPath "\Stubs\Generic.psm1" ` + -Resolve) Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` -ChildPath "\UnitTestHelper.psm1" ` -Resolve) @@ -36,14 +36,13 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "When the Task doesn't exist but it should" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Title = "Contoso Task" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + Title = "Contoso Task" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { @@ -86,13 +85,13 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { } public string ConversationThreadId {get;set;} public string OrderHint {get;set;} - public void Create(System.Management.Automation.PSCredential Credential, string ApplicationId){} - public void Update(System.Management.Automation.PSCredential Credential, string ApplicationId){} + public void Create(System.Management.Automation.PSCredential Credential){} + public void Update(System.Management.Automation.PSCredential Credential){} public string GetTaskCategoryNameByColor(string ColorName){return "";} public string GetTaskColorNameByCategory(string CategoryName){return "";} - public void PopulateById(System.Management.Automation.PSCredential Credential, string ApplicationId, string TaskId){} - public void UpdateDetails(System.Management.Automation.PSCredential Credential, string ApplicationId){} - public void Delete(System.Management.Automation.PSCredential Credential, string ApplicationId, string TaskId){} + public void PopulateById(System.Management.Automation.PSCredential Credential, string TaskId){} + public void UpdateDetails(System.Management.Automation.PSCredential Credential){} + public void Delete(System.Management.Automation.PSCredential Credential, string TaskId){} } "@ } @@ -118,25 +117,24 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task exists and is NOT in the Desired State" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - TaskId = "12345" - Title = "Contoso Task" - Priority = 4 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + TaskId = "12345" + Title = "Contoso Task" + Priority = 4 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - Id = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + Id = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } } @@ -157,27 +155,26 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task exists and is IN the Desired State" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Title = "Contoso Task" - TaskId = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Bucket = 'Bucket12345' - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + Title = "Contoso Task" + TaskId = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Bucket = 'Bucket12345' + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - Priority = 5 - Id = "12345" - PercentComplete = 75 - StartDateTime = "2020-06-09" - BucketId = 'Bucket12345' + PlanId = "1234567890" + Title = "Contoso Task" + Priority = 5 + Id = "12345" + PercentComplete = 75 + StartDateTime = "2020-06-09" + BucketId = 'Bucket12345' } } } @@ -194,25 +191,24 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task exists but it should not" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - Title = "Contoso Task" - TaskId = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Absent" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + Title = "Contoso Task" + TaskId = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Absent" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - Id = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + Id = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } } @@ -229,34 +225,33 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task is need to be part of a Bucket by ID and is in Desired State" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - TaskId = "12345" - Title = "Contoso Task" - Bucket = "Bucket12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + TaskId = "12345" + Title = "Contoso Task" + Bucket = "Bucket12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - BucketId = "Bucket12345" - Id = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + BucketId = "Bucket12345" + Id = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - Id = "Bucket12345" - Name = "TestBucket" + Id = "Bucket12345" + Name = "TestBucket" } } } @@ -274,26 +269,25 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task is need to be part of a Bucket by Name and is NOT" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - TaskId = "12345" - Title = "Contoso Task" - Bucket = "TestBucket" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + TaskId = "12345" + Title = "Contoso Task" + Bucket = "TestBucket" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - Id = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + Id = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } @@ -310,33 +304,32 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "Task should not be part of a Bucket but it IS" -Fixture { BeforeAll { $testParams = @{ - PlanId = "1234567890" - TaskId = "12345" - Title = "Contoso Task" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" - Ensure = "Present" - ApplicationId = "1234567890" - Credential = $Credential + PlanId = "1234567890" + TaskId = "12345" + Title = "Contoso Task" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" + Ensure = "Present" + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - BucketId = "Bucket12345" - Id = "12345" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + BucketId = "Bucket12345" + Id = "12345" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ - Id = "Bucket12345" - Name = "TestBucket" + Id = "Bucket12345" + Name = "TestBucket" } } } @@ -354,17 +347,16 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { BeforeAll { $Global:CurrentModeIsExport = $true $testParams = @{ - ApplicationId = "1234567890" - Credential = $Credential + Credential = $Credential } Mock -CommandName Get-MgPlannerTask -MockWith { return @{ - PlanId = "1234567890" - Title = "Contoso Task" - Priority = 5 - PercentComplete = 75 - StartDateTime = "2020-06-09" + PlanId = "1234567890" + Title = "Contoso Task" + Priority = 5 + PercentComplete = 75 + StartDateTime = "2020-06-09" } } } diff --git a/Tests/Unit/Stubs/Generic.psm1 b/Tests/Unit/Stubs/Generic.psm1 index 7a67778bfa..dcc5051663 100644 --- a/Tests/Unit/Stubs/Generic.psm1 +++ b/Tests/Unit/Stubs/Generic.psm1 @@ -1203,15 +1203,15 @@ function Connect-Graph ) } -function Get-MGGroupPlannerPlan -{ - [CmdletBinding()] - Param( - [Parameter()] - [System.String] - $GroupId - ) -} +# function Get-MGGroupPlannerPlan +# { +# [CmdletBinding()] +# Param( +# [Parameter()] +# [System.String] +# $GroupId +# ) +# } function Update-MGPlannerPlan { From 82c766c36adfe30c7f34042b6e5f023ae5276717 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Mon, 29 Aug 2022 16:36:35 +0200 Subject: [PATCH 09/10] Fixed more unit test issues --- .../DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 | 3 --- .../Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 | 3 +++ .../Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 | 3 +++ .../Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 | 3 +++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 index 9eac58b802..4641ec6489 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PlannerPlan/MSFT_PlannerPlan.psm1 @@ -368,9 +368,6 @@ function Export-TargetResource { Write-Verbose -Message $_ } - - Write-Host " |---[$j/$($plans.Length)] $($plle)" - Credential = $Credential } } diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 index 00bfd34982..3e6d899ee6 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerBucket.Tests.ps1 @@ -25,6 +25,8 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $secpasswd = ConvertTo-SecureString "Pass@word1" -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential ("tenantadmin", $secpasswd) + Mock -CommandName Save-M365DSCPartialExport -MockWith { + } Mock -CommandName Connect-Graph -MockWith { } @@ -151,6 +153,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } + $Global:PartialExportFileName = "PlannerBucket.ps1" Mock -CommandName Get-MgPlannerPlanBucket -MockWith { return @{ PlanId = "1234567890" diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 index 77f5b04523..5278cb6d8f 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerPlan.Tests.ps1 @@ -25,6 +25,8 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $secpasswd = ConvertTo-SecureString "Pass@word1" -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential ("tenantadmin@contoso.onmicrosoft.com", $secpasswd) + Mock -CommandName Save-M365DSCPartialExport -MockWith { + } Mock -CommandName Connect-Graph -MockWith { } @@ -191,6 +193,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } + $Global:PartialExportFileName = "PlannerPlan.ps1" Mock -CommandName Get-MgGroup -MockWith { return @( @{ diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 index e6b1e5fd44..5975cad1d0 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.PlannerTask.Tests.ps1 @@ -25,6 +25,8 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $secpasswd = ConvertTo-SecureString "Pass@word1" -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential ("tenantadmin", $secpasswd) + Mock -CommandName Save-M365DSCPartialExport -MockWith { + } Mock -CommandName Connect-Graph -MockWith { } @@ -350,6 +352,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } + $Global:PartialExportFileName = "PlannerTask.ps1" Mock -CommandName Get-MgPlannerTask -MockWith { return @{ PlanId = "1234567890" From 9867c55333362e1e3220f113ab534be7c33e7f59 Mon Sep 17 00:00:00 2001 From: Yorick Kuijs Date: Tue, 30 Aug 2022 08:54:38 +0200 Subject: [PATCH 10/10] Updated website documentation --- .../user-guide/get-started/authentication-and-permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/user-guide/get-started/authentication-and-permissions.md b/docs/docs/user-guide/get-started/authentication-and-permissions.md index aded8f7ac2..56d093d3de 100644 --- a/docs/docs/user-guide/get-started/authentication-and-permissions.md +++ b/docs/docs/user-guide/get-started/authentication-and-permissions.md @@ -27,8 +27,8 @@ The following table provides an overview of what authentication methods are supp | _Intune*_ | Microsoft.Graph.Authentication
(Connect-MgGraph) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Check](../../Images/check.png) | | _Office 365*_ | Microsoft.Graph.Authentication
(Connect-MgGraph) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Check](../../Images/check.png) | | _OneDrive_ | PnP.PowerShell (Connect-PnPOnline) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | -| _Power Apps_ | Microsoft.PowerApps.
Administration.PowerShell | ![Check](../../Images/check.png) | ![cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | ![cross](../../Images/cross.png) | -| _Planner*_ | Microsoft.Graph.Authentication
(Connect-MgGraph) | ![Check](../../Images/cross.png) | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Check](../../Images/check.png) | +| _Power Apps_ | Microsoft.PowerApps.
Administration.PowerShell | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | +| _Planner*_ | Microsoft.Graph.Authentication
(Connect-MgGraph) | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | | _Security & Compliance Center_ | ExchangeOnlineManagement
(Connect-IPPSSession) | ![Check](../../Images/check.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) | | _SharePoint Online_ | PnP.PowerShell
(Connect-PnPOnline) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | ![Check](../../Images/check.png) | | _Teams_ | MicrosoftTeams
(Connect-MicrosoftTeams) | ![Check](../../Images/check.png) | ![Check](../../Images/cross.png) | ![Cross](../../Images/cross.png) | ![Cross](../../Images/cross.png) |