M365DSCTools.psm1
#Region './Private/Merge-Array.ps1' 0 <# .Synopsis Merges two arrays into one new array .Description This function merges two arrays into one new one. The values in the Merge array are overwriting any existing values in the Reference array. .Parameter Reference The Reference array that is used as the starting point .Parameter Merge The Merge array that will be merged into the Reference array. .Example # Merges the Merge array into the Reference array $reference = @(1,2,3,4,5,6,7,8,9,10) $merge = @(11,12,13,14,15,16,17,18,19,20) Merge-Array -Reference $reference -Merge $merge #> function Merge-Array { param ( [Parameter(Mandatory = $true)] [System.Array] $Reference, [Parameter(Mandatory = $true)] [System.Array] $Merge ) $script:level++ Write-LogEntry -Message "Processing array: $($Merge.Count) items" -Level $script:level foreach ($item in $Merge) { switch ($item.GetType().FullName) { 'System.Collections.Hashtable' { $refItem = $Reference | Where-Object -FilterScript { ($_.ContainsKey('UniqueId') -and $_.UniqueId -eq $item.UniqueId) -or ` ($_.ContainsKey('Identity') -and $_.Identity -eq $item.Identity) -or ` ($_.ContainsKey('Id') -and $_.Identity -eq $item.Id) -or ` ($_.ContainsKey('NodeName') -and $_.NodeName -eq $item.NodeName) } if ($null -eq $refItem) { # Add item Write-LogEntry -Message " Hashtable doesn't exist in Reference. Adding." -Level $script:level $Reference += $item } else { # Compare item $script:level++ Write-LogEntry -Message 'Hashtable exists in Reference. Merging.' -Level $script:level $refItem = Merge-Hashtable -Reference $refItem -Merge $item $script:level-- } } Default { if ($Reference -notcontains $item) { $Reference += $item } } } } $script:level-- return $Reference } #EndRegion './Private/Merge-Array.ps1' 80 #Region './Private/Merge-Hashtable.ps1' 0 <# .Synopsis Merges two hashtables .Description This function merges two hashtables into one new one. The values in the Merge hashtable are overwriting any existing values in the Reference hashtable. .Parameter Reference The Reference hashtable that is used as the starting point .Parameter Merge The Merge hashtable that will be merged into the Reference hashtable. .Example # Merges the Merge file into the Reference file $reference = @{ 'Key1' = 'Value1' 'Key2' = 'Value2' 'Key3' = @{ 'Key3.1' = 'Value3.1' 'Key3.2' = 'Value3.2' } } $merge = @{ 'Key1' = 'ValueNew' 'Key3' = @{ 'Key3.2' = 'ValueNew' 'Key3.3' = 'Value3.3' } } Merge-Hashtable -Reference $reference -Merge $merge #> function Merge-Hashtable { param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Reference, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Merge ) $script:level++ $items = $Merge.GetEnumerator() foreach ($item in $items) { $itemKey = $item.Key $itemData = $item.Value Write-LogEntry -Message "Processing: $itemKey" -Level $script:level switch ($itemData.GetType().FullName) { 'System.Collections.Hashtable' { # Check if item exists in the reference if ($Reference.ContainsKey($itemKey) -eq $false) { # item does not exist, add item Write-LogEntry -Message ' Key missing in Merge object, adding key' -Level $script:level $Reference.Add($itemKey, $itemData) } else { $script:level++ Write-LogEntry -Message 'Key exists in Merge object, checking child items' -Level $script:level $Reference.$itemKey = Merge-Hashtable -Reference $Reference.$itemKey -Merge $itemData $script:level-- } } 'System.Object[]' { if ($null -eq $Reference.$itemKey -or $Reference.$itemKey.Count -eq 0) { $Reference.$itemKey = $itemData } else { $Reference.$itemKey = [Array](Merge-Array -Reference $Reference.$itemKey -Merge $itemData) } } Default { if ($Reference.$itemKey -ne $itemData) { $Reference.$itemKey = $itemData } } } } $script:level-- return $Reference } #EndRegion './Private/Merge-Hashtable.ps1' 99 #Region './Private/Write-LogEntry.ps1' 0 <# .Synopsis Writes a log entry to the console, including a timestamp .Description This function writes a log entry to the console, including a timestamp of the current time. .Parameter Message The message that has to be written to the console. .Parameter Level The number of spaces the message has to be indented. .Example Write-LogEntry -Message 'This is a log entry' .Example Write-LogEntry -Message 'This is an indented log entry' -Level 1 #> function Write-LogEntry { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Using Write-Host to force output to the screen instead of into the pipeline.')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Message, [Parameter()] [System.Int32] $Level = 0 ) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $indentation = ' ' * $Level $output = '[{0}] - {1}{2}' -f $timestamp, $indentation, $Message Write-Host -Object $output } #EndRegion './Private/Write-LogEntry.ps1' 42 #Region './Public/Add-ModulesToBlobStorage.ps1' 0 <# .Synopsis Downloads all Microsoft365DSC dependencies and uploads these to an Azure Blob Storage .Description This function checks which dependencies the used version of Microsoft365DSC requires and downloads these from the PowerShell Gallery. The dependencies are then packaged into a zip file and uploaded to an Azure Blob Storage. .Parameter ResourceGroupName The Azure Resource Group Name where the Storage Account is located .Parameter StorageAccountName The name of the Storage Account where the zip file will be uploaded to .Parameter ContainerName The name of the Container where the zip file will be uploaded to .Example Add-ModulesToBlobStorage -ResourceGroupName 'MyResourceGroup' -StorageAccountName 'MyStorageAccount' -ContainerName 'MyContainer' #> function Add-ModulesToBlobStorage { [CmdletBinding()] param ( # [Parameter(Mandatory = $true)] # [System.String] # $SubscriptionName, [Parameter(Mandatory = $true)] [System.String] $ResourceGroupName, [Parameter(Mandatory = $true)] [System.String] $StorageAccountName, [Parameter(Mandatory = $true)] [System.String] $ContainerName ) Write-LogEntry -Message 'Upload dependencies to storage container' Write-LogEntry -Message "Connecting to storage account '$StorageAccountName'" -Level 1 $storageAcc = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName Write-LogEntry -Message 'Retrieving storage account context' -Level 1 $context = $storageAcc.Context Write-LogEntry -Message 'Checking dependencies' $m365Module = Get-Module Microsoft365Dsc -ListAvailable $modulePath = Split-Path -Path $m365Module.Path -Parent $versionString = $m365Module.Version.ToString() -replace '\.', '_' $dependenciesPath = Join-Path -Path $modulePath -ChildPath 'Dependencies\Manifest.psd1' if (Test-Path -Path $dependenciesPath) { Write-LogEntry -Message 'Downloading dependencies' -Level 1 $savePath = Join-Path -Path $env:TEMP -ChildPath 'M365DSCModules' if ((Test-Path -Path $savePath) -eq $false) { $null = New-Item -Path $savePath -ItemType 'Directory' } $data = Import-PowerShellDataFile -Path $dependenciesPath foreach ($dependency in $data.Dependencies) { Write-LogEntry -Message ('Saving {0} (v{1})' -f $dependency.ModuleName, $dependency.RequiredVersion) -Level 1 Save-Module -Name $dependency.ModuleName -RequiredVersion $dependency.RequiredVersion -Path $savePath } Write-LogEntry -Message 'Packaging Zip file' -Level 1 $zipFileName = "M365DSCDependencies-$versionString.zip" $zipFilePath = Join-Path -Path $env:TEMP -ChildPath $zipFileName if ((Test-Path -Path $zipFilePath)) { Write-LogEntry -Message "$zipFileName already exist on disk. Removing!" -Level 1 Remove-Item -Path $zipFilePath -Confirm:$false } Compress-Archive -Path $savePath\* -DestinationPath $zipFilePath Write-LogEntry -Message 'Uploading Zip file' -Level 1 $blobContent = Get-AzStorageBlob -Container $ContainerName -Context $context -Prefix $zipFileName if ($null -ne $blobContent) { Write-LogEntry -Message "$zipFileName already exist in the Blob Storage. Removing!" -Level 2 $blobContent | Remove-AzStorageBlob } $null = Set-AzStorageBlobContent -Container $ContainerName -File $zipFilePath -Context $context -Force Write-LogEntry -Message 'Removing temporary components' Remove-Item -Path $savePath -Recurse -Confirm:$false -Force Remove-Item -Path $zipFilePath -Confirm:$false } else { Write-LogEntry -Message '[ERROR] Dependencies\Manifest.psd1 file not found' } } #EndRegion './Public/Add-ModulesToBlobStorage.ps1' 105 #Region './Public/Get-ModulesFromBlobStorage.ps1' 0 <# .Synopsis Downloads all Microsoft365DSC dependencies and uploads these to an Azure Blob Storage .Description This function checks which dependencies the used version of Microsoft365DSC requires and downloads these from the PowerShell Gallery. The dependencies are then packaged into a zip file and uploaded to an Azure Blob Storage. .Parameter ResourceGroupName The Azure Resource Group Name where the Storage Account is located .Parameter StorageAccountName The name of the Storage Account where the zip file will be uploaded to .Parameter ContainerName The name of the Container where the zip file will be uploaded to .Parameter Version The version of Microsoft365DSC for which the prerequisites should be retrieved .Example Get-ModulesFromBlobStorage -ResourceGroupName 'MyResourceGroup' -StorageAccountName 'MyStorageAccount' -ContainerName 'MyContainer' -Version 1.23.530.1 #> function Get-ModulesFromBlobStorage { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ResourceGroupName, [Parameter(Mandatory = $true)] [System.String] $StorageAccountName, [Parameter(Mandatory = $true)] [System.String] $ContainerName, [Parameter(Mandatory = $true)] [System.String] $Version ) Write-LogEntry -Message "Download dependencies from storage container. M365DSC Version $Version." -Level 2 Write-LogEntry -Message "Connecting to storage account '$StorageAccountName'" -Level 3 $storageAcc = Get-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageAccountName Write-LogEntry -Message 'Retrieving storage account context' -Level 3 $context = $storageAcc.Context Write-LogEntry -Message 'Checking download folder existence' -Level 3 $destination = Join-Path -Path $env:TEMP -ChildPath 'M365DSCModules' if ((Test-Path -Path $destination) -eq $false) { Write-LogEntry -Message "Creating destination folder: '$destination'" -Level 4 $null = New-Item -ItemType Directory -Path $destination } Write-LogEntry -Message 'Downloading the blob contents from the container' -Level 2 $prefix = 'M365DSCDependencies-' + ($version -replace '\.', '_') $blobContent = Get-AzStorageBlob -Container $ContainerName -Context $context -Prefix $prefix if ($null -eq $blobContent) { Write-LogEntry -Message "[ERROR] No files found that match the pattern: '$prefix'" -Level 2 } else { Write-LogEntry -Message "Downloading $($blobContent.Name) to $destination" -Level 3 $downloadFile = Join-Path -Path $destination -ChildPath $blobContent.Name if (Test-Path -Path $downloadFile) { Write-LogEntry -Message "$downloadFile already exists. Removing!" -Level 3 Remove-Item -Path $downloadFile -Confirm:$false } $null = Get-AzStorageBlobContent -Container $ContainerName -Context $context -Blob $blobContent.Name -Destination $destination -Force Write-LogEntry -Message "Extracting $($blobContent.Name)" -Level 2 $extractPath = Join-Path -Path $destination -ChildPath $Version.ToString() if (Test-Path -Path $extractPath) { Write-LogEntry -Message "$extractPath already exists. Removing!" -Level 3 Remove-Item -Path $extractPath -Recurse -Confirm:$false } Expand-Archive -Path $downloadFile -DestinationPath $extractPath Write-LogEntry -Message "Copying folders in $extractPath to 'C:\Program Files\WindowsPowerShell\Modules'" -Level 2 Copy-Item -Path "$extractPath\*" -Destination 'C:\Program Files\WindowsPowerShell\Modules' -Recurse -Container -Force Write-LogEntry -Message 'Removing temporary components' -Level 2 Remove-Item -Path $extractPath -Recurse -Confirm:$false Remove-Item -Path $destination -Recurse -Confirm:$false } } #EndRegion './Public/Get-ModulesFromBlobStorage.ps1' 99 #Region './Public/Merge-DataFile.ps1' 0 <# .Synopsis Merges two PowerShell Data File hashtables .Description This function merges two PowerShell Data file hashtables into one new one. The values in the Merrge hashtable are overwriting any existing values in the Reference hashtable. .Parameter Reference The Reference hashtable that is used as the starting point .Parameter Merge The Merge hashtable that will be merged into the Reference hashtable. .Example # Merges the Merge file into the Reference file $reference = Import-PowerShellDataFile -Path 'reference.psd1' $merge = Import-PowerShellDataFile -Path 'merge.psd1' Merge-DataFile -Reference $reference -Merge $merge #> function Merge-DataFile { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Reference, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Merge ) Begin { $script:level = 0 Write-LogEntry -Message 'Starting Data Merge' -Level $script:level $ref = $Reference.Clone() $mer = $Merge.Clone() } Process { $result = Merge-Hashtable -Reference $ref -Merge $mer } End { Write-LogEntry -Message 'Data Merge Completed' -Level $script:level return $result } } #EndRegion './Public/Merge-DataFile.ps1' 58 |