Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring: functions that check if new version of the module itself or newer dependencies are available #2232

Closed
andikrueger opened this issue Aug 26, 2022 · 0 comments · Fixed by #2406 or #2421
Labels
Core Engine Enhancement New feature or request

Comments

@andikrueger
Copy link
Collaborator

At the moment, there are several different approaches on how to verify that the latest and greatest version of M365DSC is installed and to check if there are new dependencies available. Also there are various checks, if the dependencies are installed correctly and no older versions of the module/dependencies are present.

Following a list of functions that handle some parts of all these mechanisms.

It would be great to unify these functions to make it easier to maintain.

Test- Methods

Test-M365DSCDependenciesForNewVersions

<#
.Description
This function checks if new versions are available for the M365DSC dependencies
.Example
Test-M365DSCDependenciesForNewVersions
.Functionality
Public
#>
function Test-M365DSCDependenciesForNewVersions
{
[CmdletBinding()]
$InformationPreference = 'Continue'
$currentPath = Join-Path -Path $PSScriptRoot -ChildPath '..\' -Resolve
$manifest = Import-PowerShellDataFile "$currentPath/Dependencies/Manifest.psd1"
$dependencies = $manifest.Dependencies
$i = 1
Import-Module PowerShellGet -Force
foreach ($dependency in $dependencies)
{
Write-Progress -Activity "Scanning Dependencies" -PercentComplete ($i / $dependencies.Count * 100)
try
{
$moduleInGallery = Find-Module $dependency.ModuleName
[array]$moduleInstalled = Get-Module $dependency.ModuleName -ListAvailable | Select-Object Version
if ($moduleInstalled)
{
$modules = $moduleInstalled | Sort-Object Version -Descending
}
$moduleInstalled = $modules[0]
if (-not $modules -or [Version]($moduleInGallery.Version) -gt [Version]($moduleInstalled[0].Version))
{
Write-Host "New version of {$($dependency.ModuleName)} is available {$($moduleInGallery.Version)}"
}
}
catch
{
Write-Host $_
Write-Host "New version of {$($dependency.ModuleName)} is available"
}
$i++
}
}

Test-M365DSCNewVersionAvailable

<#
.Description
This function check if the currently installed version of M365DSC is the most recent one,
available in the PowerShell Gallery
.Example
Test-M365DSCNewVersionAvailable
.Functionality
Public
#>
function Test-M365DSCNewVersionAvailable
{
[CmdletBinding()]
param()
try
{
if ($null -eq $Global:M365DSCNewVersionNotification)
{
# Get current module used
$currentVersion = Get-Module 'Microsoft365DSC' -ErrorAction Stop
# Get module in the Gallery
$JobID = Start-Job { Find-Module 'Microsoft365DSC' -ErrorAction Stop }
$Timeout = $true
for ($i = 0; $i -lt 10; $i++)
{
if ((Get-Job $JobID.id).State -notmatch 'Running')
{
$Timeout = $false
break;
}
Start-Sleep -Seconds 1
}
if ($Timeout)
{
return
}
$GalleryVersion = Get-Job $JobID.id | Receive-Job
if ([Version]($GalleryVersion.Version) -gt [Version]($currentVersion.Version))
{
$message = "A NEWER VERSION OF MICROSOFT365DSC {v$($GalleryVersion.Version)} IS AVAILABLE IN THE POWERSHELL GALLERY. TO UPDATE, RUN:`r`nInstall-Module Microsoft365DSC -Force -AllowClobber"
Write-Host $message `
-ForegroundColor 'White' `
-BackgroundColor 'DarkGray'
Write-Verbose -Message $message
}
$Global:M365DSCNewVersionNotification = 'AlreadyShown'
}
}
catch
{
Write-Verbose -Message $_
Add-M365DSCEvent -Message $_ -EntryType 'Error' `
-EventID 1 -Source $($MyInvocation.MyCommand.Source)
}
}

Test-M365DSCModuleValiditiy

<#
.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.
.Parameter Force
Specifies that all dependencies should be forcefully imported again.
.Example
Test-M365DSCModuleValidity
.Example
Test-M365DSCModuleValidity -Force
.Functionality
Public
#>
function Test-M365DSCModuleValidity
{
[CmdletBinding()]
param(
)
$InformationPreference = 'Continue'
# validate only one installation of the module is present (and it's the latest version available from the psgallery)
$latestVersion = (Find-Module -Name 'Microsoft365DSC').Version
$localVersion = (Get-Module -Name 'Microsoft365DSC').Version
if ($latestVersion -gt $localVersion)
{
Write-Host "There is a newer version of the 'Microsoft365DSC' module available on the gallery."
Write-Host "To update the module and it's dependencies, run the following commands:"
Write-Host "Update-Module -Name 'Microsoft365DSC' -Force`nUpdate-M365DSCDependencies -Force`nUninstall-M365DSCOutdatedDependencies" -ForegroundColor Blue
}
}

Test-M365DSCDependencies

<#
.Description
This function checks if all M365DSC dependencies are installed
.Functionality
Internal, Hidden
#>
function Test-M365DSCDependencies
{
[CmdletBinding()]
$currentPath = Join-Path -Path $PSScriptRoot -ChildPath '..\' -Resolve
$manifest = Import-PowerShellDataFile "$currentPath/Dependencies/Manifest.psd1"
$dependencies = $manifest.Dependencies
$missingDependencies = @()
foreach ($dependency in $dependencies)
{
try
{
Write-Verbose -Message "{$($dependency.ModuleName)} version $($dependency.RequiredVersion)"
$module = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $dependency.RequiredVersion }
if (-not $module)
{
$missingDependencies += $dependency
}
}
catch
{
Write-Verbose -Message "Error: $_"
}
}
return $missingDependencies
}

Uninstall- and Remove- Methods

Uninstall-M365DSCOutdatedDependencies

<#
.Description
This function uninstalls all previous M365DSC dependencies and older versions of the module.
.Example
Uninstall-M365DSCOutdatedDependencies
.Functionality
Public
#>
function Uninstall-M365DSCOutdatedDependencies
{
[CmdletBinding()]
param(
)
$InformationPreference = 'Continue'
[array]$microsoft365DscModules = Get-Module Microsoft365DSC -ListAvailable
$outdatedMicrosoft365DscModules = $microsoft365DscModules | Sort-Object Version | Select-Object -SkipLast 1
foreach ($module in $outdatedMicrosoft365DscModules)
{
try
{
Write-Information -Message "Uninstalling $($module.Name) Version {$($module.Version)}"
if (Test-Path -Path $($module.Path))
{
Remove-Item $($module.Path) -Force -Recurse
}
}
catch
{
Write-Host "Could not uninstall $($module.Name) Version $($module.Version) "
}
}
$currentPath = Join-Path -Path $PSScriptRoot -ChildPath '..\' -Resolve
$manifest = Import-PowerShellDataFile "$currentPath\Dependencies\Manifest.psd1"
$allDependenciesExceptAuth = $manifest.Dependencies | Where-Object { $_.ModuleName -ne "Microsoft.Graph.Authentication" }
$i = 1
foreach ($dependency in $allDependenciesExceptAuth)
{
Write-Progress -Activity "Scanning Dependencies" -PercentComplete ($i / $allDependenciesExceptAuth.Count * 100)
try
{
$found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -ne $dependency.RequiredVersion }
foreach ($foundModule in $found)
{
try
{
Write-Information -Message "Uninstalling $($foundModule.Name) Version {$($foundModule.Version)}"
if (Test-Path -Path $($foundModule.Path))
{
Remove-Item $($foundModule.ModuleBase) -Force -Recurse
}
}
catch
{
Write-Host "Could not uninstall $($foundModule.Name) Version $($foundModule.Version) "
}
}
}
catch
{
Write-Host "Could not uninstall {$($dependency.ModuleName)}"
}
$i++
}
$authModule = $manifest.Dependencies | Where-Object { $_.ModuleName -eq "Microsoft.Graph.Authentication" }
try
{
Write-Information -Message "Checking Microsoft.Graph.Authentication"
$found = Get-Module $authModule.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -ne $authModule.RequiredVersion }
foreach ($foundModule in $found)
{
try
{
Write-Information -Message "Uninstalling $($foundModule.Name) version {$($foundModule.Version)}"
if (Test-Path -Path $($foundModule.Path))
{
Remove-Item $($foundModule.ModuleBase) -Force -Recurse
}
}
catch
{
Write-Host "Could not uninstall $($foundModule.Name) Version $($foundModule.Version) "
}
}
}
catch
{
Write-Host "Could not uninstall {$($dependency.ModuleName)}"
}
}

Remove-M365DSCInvalidDependenciesFromSession

<#
.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
}
}
}
}
}

Update- Methods

Update-M365DSCDependencies

<#
.Description
This function installs all missing M365DSC dependencies
.Parameter Force
Specifies that all dependencies should be forcefully imported again.
.Example
Update-M365DSCDependencies
.Example
Update-M365DSCDependencies -Force
.Functionality
Public
#>
function Update-M365DSCDependencies
{
[CmdletBinding()]
param(
[parameter()]
[Switch]
$Force
)
$InformationPreference = 'Continue'
$currentPath = Join-Path -Path $PSScriptRoot -ChildPath '..\' -Resolve
$manifest = Import-PowerShellDataFile "$currentPath/Dependencies/Manifest.psd1"
$dependencies = $manifest.Dependencies
$i = 1
if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
{
foreach ($dependency in $dependencies)
{
Write-Progress -Activity "Scanning Dependencies" -PercentComplete ($i / $dependencies.Count * 100)
try
{
if (-not $Force)
{
$found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $dependency.RequiredVersion }
}
if (-not $found -or $Force)
{
Write-Information -Message "Installing $($dependency.ModuleName) version {$($dependency.RequiredVersion)}"
Install-Module $dependency.ModuleName -RequiredVersion $dependency.RequiredVersion -AllowClobber -Force -Scope 'AllUsers'
}
}
catch
{
Write-Host "Could not update {$($dependency.ModuleName)}"
}
$i++
}
}
else
{
Write-Error "Cannot update the dependencies for Microsoft365DSC. You need to run this command as a local administrator."
}
}

@andikrueger andikrueger added Enhancement New feature or request Core Engine labels Aug 26, 2022
NikCharlebois added a commit to NikCharlebois/Microsoft365DSC that referenced this issue Oct 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Core Engine Enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant