Er zijn twee zekerheden in het leven:
- Je drinkt koffie en die wordt koud.
- Op het moment dat je een PowerShell-script nodig hebt, ontbreekt er altijd nét weer een module.
En dat tweede begon me nogal te irriteren.
Elke keer als ik een machine opnieuw had ingericht, een frisse sessie startte of weer op een andere omgeving werkte, begon hetzelfde toneelstuk opnieuw:
module missen, module updaten, PSGallery corrigeren, repository fixen, transcript starten, logging regelen… en pas daarna kon ik eindelijk doen waarvoor ik PowerShell überhaupt had geopend.
Dus heb ik gedaan wat iedere licht gefrustreerde beheerder uiteindelijk doet:
ik heb er gewoon een script voor geschreven.
Niet omdat het leuk was.
Nou ja, een beetje wel.
Maar vooral omdat ik het zat was om steeds handmatig hetzelfde circus op te tuigen.
Waarom ik dit script heb gebouwd
Dit script is ontstaan uit pure praktische irritatie.
Ik merkte dat ik bij mijn beheerwerkzaamheden steeds afhankelijk was van dezelfde set modules. En natuurlijk werkt het altijd zo dat precies die ene module ontbreekt op het moment dat je snel iets moet regelen.
Dus in plaats van elke keer dit te doen:
- controleren of PSGallery nog leeft;
- kijken of NuGet nog meewerkt;
- modules handmatig installeren;
- modules bijwerken;
- transcript starten;
- logging regelen;
- hopen dat het deze keer wél netjes loopt;
… heb ik besloten dat PowerShell dat probleem zelf maar moest oplossen.
Deze bootstrap wordt nu geladen zodra ik een specifiek ander script uitvoer. Met andere woorden: vóórdat het echte werk begint, ruimt dit script eerst de rommel op. Een digitale versie van even de vaatwasser uitruimen voordat je gaat koken.
Wat dit script precies doet
De nieuwste versie van mijn script doet meer dan alleen “even modules installeren”.
Het script:
- controleert logging en transcript;
- maakt benodigde mappen aan zoals
C:\Temp,C:\TmpenC:\Log; - configureert PowerShell Gallery en PSResourceGet;
- controleert of benodigde modules aanwezig zijn;
- installeert of werkt modules automatisch bij;
- ondersteunt zowel productie- als bètamodules;
- kan modules direct importeren in de sessie;
- logt netjes welke gebruiker, machine en IP-adressen zijn gebruikt;
- kopieert het transcript zelfs naar de Intune-logmap als die aanwezig is.
Oftewel: het is niet zomaar een installatiescript, maar eerder een kleine PowerShell-conciërge die eerst alles netjes klaarzet voordat jij binnenkomt.
Wat er nieuw en beter is in deze versie
De update naar versie 3.1.1 is niet alleen cosmetisch. Hier zitten echt nuttige verbeteringen in.
1. Slimmere moduledefinities
De modules worden nu niet meer alleen als simpele namen verwerkt, maar ook als objecten en hashtables. Daardoor kan ik veel flexibeler werken met modulelijsten.
Dat klinkt heel professioneel, maar komt eigenlijk neer op:
ik kan PowerShell nu eindelijk fatsoenlijk vertellen wat ik wil, zonder dat het script daar emotioneel van instort.
2. Ondersteuning voor pinned modules
Sommige modules wil je niet automatisch naar de nieuwste versie trekken. Soms heb je gewoon een specifieke versie nodig omdat “nieuwer” niet altijd hetzelfde is als “beter”.
Daarom kan ik nu modules vastzetten op een vereiste versie, zoals:
CIS-M365-Benchmark op versie 5.2.0
Dat voorkomt verrassingen.
En met verrassingen bedoel ik natuurlijk: ellende.
3. Bootstrap-modules apart geregeld
De basis is nu opgesplitst in een eigen bootstrap-set met:
- PackageManagement
- PowerShellGet
- Microsoft.PowerShell.PSResourceGet
Dat is slim, want als je afhankelijk bent van modulebeheer, moet je eerst zorgen dat je modulebeheer zélf op orde is. Een beetje alsof je eerst je gereedschapskist repareert voordat je de schuurdeur gaat fiksen.
4. Nettere fallback-logica
Als Find-Module, Install-Module of Update-Module niet meewerkt, probeert het script automatisch alternatieven via PSResourceGet.
Oftewel:
als de voordeur dicht zit, probeert het script gewoon de achterdeur.
Heel beschaafd overigens. Geen koevoet, gewoon nette PowerShell-logica.
Productie of bèta? Allebei kan
Het script ondersteunt twee modi:
Production
Voor de dagelijkse, serieuze beheerwerkzaamheden met modules zoals:
- Az.Accounts
- Microsoft.Graph
- Microsoft.Entra
- PnP.PowerShell
- ExchangeOnlineManagement
- MicrosoftTeams
- CIS-M365-Benchmark
Beta
Voor de momenten waarop je denkt:
“Ik wil vooruitgang, spanning en een klein beetje risico.”
Dan kun je kiezen voor:
- Az.Accounts
- Microsoft.Graph.Beta
- Microsoft.Entra.Beta
Waarom dit voor mij prettig werkt
Wat ik zelf fijn vind aan deze aanpak, is dat ik er daarna niet meer over hoef na te denken.
Ik start mijn andere script.
Deze bootstrap draait eerst.
Alle basisvoorwaarden worden gecontroleerd.
Modules worden bijgewerkt of geïnstalleerd.
Eventueel worden ze direct geladen.
En pas daarna begint het echte werk.
Dat betekent dus minder:
- “Waarom doet dit script niets?”
- “Oh wacht, module ontbreekt.”
- “Waarom vertrouwt PSGallery zichzelf weer niet?”
- “Waarom ben ik alweer handmatig dingen aan het fixen?”
En meer:
- script starten;
- koffie pakken;
- doen waarvoor ik achter mijn laptop zat.
Het soort automatisering waar je echt blij van wordt
Niet alle automatisering hoeft groots, hip of AI-achtig te zijn.
Soms is de beste automatisering gewoon:
iets bouwen dat jouw eigen irritatie oplost.
Dat is precies wat dit script doet.
Geen glamour.
Geen marketingpraat.
Gewoon een robuust PowerShell-script dat voorkomt dat ik iedere keer opnieuw dezelfde administratieve strafwerkzaamheden moet uitvoeren.
En eerlijk?
Dat zijn vaak de fijnste scripts.
De scripts die niet indrukwekkend staan in een demo, maar die je op een drukke werkdag wél redden van nodeloos gevloek.
Script

param(
[switch]$Silent,
[switch]$Beta,
[switch]$ImportModules,
[switch]$ForceInstall
)
<#
.NOTES
============================================================================
Created on: 2026-03-18
Created by: Vincent van Unen
Filename: SCA_PowerShell-Modules.ps1
============================================================================
.DESCRIPTION
Initialisatiescript voor MSP-beheerwerkzaamheden.
Het script controleert:
- logging en transcript
- mappenstructuur
- PSGallery- en PSResourceGet-configuratie
- aanwezigheid van vereiste PowerShell-modules
Standaard worden productiemodules verwerkt.
Gebruik -Beta om uitsluitend bètamodules te verwerken.
.SYNOPSIS
Initialiseert PowerShell-modules met logging-, transcript- en repositoryfunctionaliteit.
.EXAMPLE
.\SCA_PowerShell-Modules.ps1
Voert het script uit in productiemodus met standaardmodules.
.EXAMPLE
.\SCA_PowerShell-Modules.ps1 -ImportModules
Installeert of werkt modules bij en importeert deze direct in de sessie.
.EXAMPLE
.\SCA_PowerShell-Modules.ps1 -Beta -ForceInstall
Verwerkt uitsluitend bètamodules en forceert installatie of update.
#>
#region Script metadata
$ScriptAuthor = 'Vincent van Unen'
$ScriptVersion = '3.1.1'
$ScriptChangeDate = '2026-03-19'
$ScriptChangeLog = 'Bugfix voor hashtable-moduledefinities en ondersteuning voor pinned modules, inclusief CIS-M365-Benchmark'
$ScriptCurrentUser = $env:UserName
$ScriptRunningDevice = $env:COMPUTERNAME
$CurrentDate = Get-Date -Format 'yyyy-MM-dd'
$LogName = 'Initialize-SCA-Modules'
$ScriptMode = if ($Beta) { 'Beta' } else { 'Production' }
#endregion Script metadata
#region Security and session settings
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
} catch {}
try {
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope Process -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
} catch {}
$MaximumFunctionCount = 16384
$MaximumVariableCount = 16384
#endregion Security and session settings
#region Directories
$TempDir1 = 'C:\Temp'
$TempDir2 = 'C:\Tmp'
$LogDir = 'C:\Log'
foreach ($Folder in @($TempDir1, $TempDir2, $LogDir)) {
if (-not (Test-Path -Path $Folder)) {
New-Item -ItemType Directory -Path $Folder -Force | Out-Null
}
}
#endregion Directories
#region Logging functions
function Write-SCA_Status {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Message,
[ValidateSet('INFO', 'WARN', 'ERROR', 'SUCCESS', 'DEBUG')]
[string]$Level = 'INFO'
)
if ($Silent) {
return
}
switch ($Level) {
'INFO' { Write-Host "[INFO] $Message" -ForegroundColor Cyan }
'WARN' { Write-Host "[WARN] $Message" -ForegroundColor Yellow }
'ERROR' { Write-Host "[ERROR] $Message" -ForegroundColor Red }
'SUCCESS' { Write-Host "[OK] $Message" -ForegroundColor Green }
'DEBUG' { Write-Host "[DEBUG] $Message" -ForegroundColor DarkGray }
}
}
function Write-SCA_LogFile {
[CmdletBinding()]
param(
[ValidateSet('INFO', 'WARN', 'ERROR', 'FATAL', 'DEBUG', 'SUCCESS')]
[string]$Level = 'INFO',
[Parameter(Mandatory = $true)]
[string]$Message,
[string]$LogFile = "$LogDir\$LogName-$CurrentDate.log"
)
try {
if (-not (Test-Path -Path $LogFile)) {
New-Item -Path $LogFile -ItemType File -Force | Out-Null
}
if ([string]::IsNullOrWhiteSpace($Message)) {
Add-Content -Path $LogFile -Value ''
}
else {
$DateStamp = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss.fff')
Add-Content -Path $LogFile -Value "[$DateStamp] [$Level] $Message"
}
}
catch {
Write-Host "Log schrijven is mislukt: $($_.Exception.Message)" -ForegroundColor Red
}
}
#endregion Logging functions
#region Helper functions
function Test-SCA_CommandExists {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
return [bool](Get-Command -Name $Name -ErrorAction SilentlyContinue)
}
function Get-SCA_PublicIpAddress {
[CmdletBinding()]
param()
try {
return (Invoke-RestMethod -Uri 'https://ifconfig.me/ip' -TimeoutSec 10)
}
catch {
return 'Onbekend'
}
}
function Get-SCA_PrivateIpAddress {
[CmdletBinding()]
param()
try {
$Addresses = Get-NetIPAddress -ErrorAction Stop |
Where-Object {
$_.AddressFamily -eq 'IPv4' -and
$_.IPAddress -notlike '169.254*' -and
$_.IPAddress -ne '127.0.0.1'
} |
Select-Object -ExpandProperty IPAddress
if ($Addresses) {
return ($Addresses -join ', ')
}
return 'Onbekend'
}
catch {
return 'Onbekend'
}
}
function ConvertTo-SCA_ModuleObject {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[object]$Module
)
if ($Module -is [string]) {
return [pscustomobject]@{
Name = $Module
RequiredVersion = $null
}
}
if ($Module -is [hashtable]) {
if (-not $Module.ContainsKey('Name')) {
throw 'Ongeldige moduledefinitie: sleutel "Name" ontbreekt.'
}
return [pscustomobject]@{
Name = [string]$Module['Name']
RequiredVersion = if ($Module.ContainsKey('RequiredVersion')) { [string]$Module['RequiredVersion'] } else { $null }
}
}
if ($Module.PSObject.Properties.Name -contains 'Name') {
$RequiredVersion = $null
if ($Module.PSObject.Properties.Name -contains 'RequiredVersion') {
$RequiredVersion = [string]$Module.RequiredVersion
}
return [pscustomobject]@{
Name = [string]$Module.Name
RequiredVersion = $RequiredVersion
}
}
throw "Ongeldige moduledefinitie: $($Module | Out-String)"
}
function Initialize-SCA_PowerShellGet {
[CmdletBinding()]
param()
if ($PSVersionTable.PSVersion.Major -ge 6) {
Write-SCA_Status -Message 'PowerShell 6+ gedetecteerd. Oude PowerShellGet-initialisatie wordt beperkt toegepast.' -Level DEBUG
Write-SCA_LogFile -Message 'PowerShell 6+ gedetecteerd. Oude PowerShellGet-initialisatie wordt beperkt toegepast.' -Level DEBUG
}
try {
$NuGetProvider = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
if (-not $NuGetProvider) {
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser -ErrorAction Stop
Write-SCA_Status -Message 'NuGet-provider is geïnstalleerd.' -Level SUCCESS
Write-SCA_LogFile -Message 'NuGet-provider is geïnstalleerd.' -Level SUCCESS
}
else {
Write-SCA_Status -Message 'NuGet-provider is beschikbaar.' -Level SUCCESS
Write-SCA_LogFile -Message 'NuGet-provider is beschikbaar.' -Level SUCCESS
}
}
catch {
Write-SCA_Status -Message "NuGet-provider kon niet worden geïnitialiseerd: $($_.Exception.Message)" -Level ERROR
Write-SCA_LogFile -Message "NuGet-provider kon niet worden geïnitialiseerd: $($_.Exception.Message)" -Level ERROR
throw
}
try {
$InstalledPowerShellGet = Get-Module -ListAvailable -Name PowerShellGet |
Sort-Object Version -Descending |
Select-Object -First 1
if (-not $InstalledPowerShellGet -or $InstalledPowerShellGet.Version -lt [version]'2.2.5') {
Install-Module -Name PowerShellGet -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
Write-SCA_Status -Message 'PowerShellGet is geïnstalleerd of bijgewerkt.' -Level SUCCESS
Write-SCA_LogFile -Message 'PowerShellGet is geïnstalleerd of bijgewerkt.' -Level SUCCESS
}
else {
Write-SCA_Status -Message "PowerShellGet is actueel. Versie: $($InstalledPowerShellGet.Version)" -Level SUCCESS
Write-SCA_LogFile -Message "PowerShellGet is actueel. Versie: $($InstalledPowerShellGet.Version)" -Level SUCCESS
}
}
catch {
Write-SCA_Status -Message "PowerShellGet kon niet worden bijgewerkt: $($_.Exception.Message)" -Level WARN
Write-SCA_LogFile -Message "PowerShellGet kon niet worden bijgewerkt: $($_.Exception.Message)" -Level WARN
}
}
function Ensure-SCA_PowerShellGallery {
[CmdletBinding()]
param()
Write-SCA_Status -Message 'PowerShell Gallery en package-management configureren...' -Level INFO
Write-SCA_LogFile -Message 'PowerShell Gallery en package-management configureren...' -Level INFO
try {
Initialize-SCA_PowerShellGet
try {
$PSGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue
if (-not $PSGallery) {
Register-PSRepository -Default -ErrorAction Stop
Write-SCA_Status -Message 'PSGallery is opnieuw geregistreerd.' -Level SUCCESS
Write-SCA_LogFile -Message 'PSGallery is opnieuw geregistreerd.' -Level SUCCESS
$PSGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction Stop
}
if ($PSGallery.InstallationPolicy -ne 'Trusted') {
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -ErrorAction Stop
Write-SCA_Status -Message 'PSGallery is ingesteld op Trusted.' -Level SUCCESS
Write-SCA_LogFile -Message 'PSGallery is ingesteld op Trusted.' -Level SUCCESS
}
else {
Write-SCA_Status -Message 'PSGallery staat al op Trusted.' -Level SUCCESS
Write-SCA_LogFile -Message 'PSGallery staat al op Trusted.' -Level SUCCESS
}
}
catch {
Write-SCA_Status -Message "PSGallery-configuratie via PowerShellGet is mislukt: $($_.Exception.Message)" -Level WARN
Write-SCA_LogFile -Message "PSGallery-configuratie via PowerShellGet is mislukt: $($_.Exception.Message)" -Level WARN
}
if (Test-SCA_CommandExists -Name 'Register-PSResourceRepository') {
try {
$PSResourceRepo = Get-PSResourceRepository -Name 'PSGallery' -ErrorAction SilentlyContinue
if (-not $PSResourceRepo) {
Register-PSResourceRepository -Name 'PSGallery' -Uri 'https://www.powershellgallery.com/api/v2' -Trusted -ErrorAction Stop
Write-SCA_Status -Message 'PSResourceGet-repository PSGallery is geregistreerd.' -Level SUCCESS
Write-SCA_LogFile -Message 'PSResourceGet-repository PSGallery is geregistreerd.' -Level SUCCESS
}
else {
Write-SCA_Status -Message 'PSResourceGet-repository PSGallery is beschikbaar.' -Level SUCCESS
Write-SCA_LogFile -Message 'PSResourceGet-repository PSGallery is beschikbaar.' -Level SUCCESS
}
}
catch {
Write-SCA_Status -Message "PSResourceGet-repositoryconfiguratie is mislukt: $($_.Exception.Message)" -Level WARN
Write-SCA_LogFile -Message "PSResourceGet-repositoryconfiguratie is mislukt: $($_.Exception.Message)" -Level WARN
}
}
}
catch {
Write-SCA_Status -Message "Repositoryconfiguratie is mislukt: $($_.Exception.Message)" -Level ERROR
Write-SCA_LogFile -Message "Repositoryconfiguratie is mislukt: $($_.Exception.Message)" -Level ERROR
throw
}
}
function Get-SCA_InstalledModuleVersion {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
try {
if (Test-SCA_CommandExists -Name 'Get-InstalledPSResource') {
$InstalledPSResource = Get-InstalledPSResource -Name $Name -ErrorAction SilentlyContinue |
Sort-Object Version -Descending |
Select-Object -First 1
if ($InstalledPSResource) {
return [pscustomobject]@{
Name = $InstalledPSResource.Name
Version = $InstalledPSResource.Version
Source = 'PSResourceGet'
}
}
}
$InstalledModule = Get-InstalledModule -Name $Name -ErrorAction SilentlyContinue |
Sort-Object Version -Descending |
Select-Object -First 1
if ($InstalledModule) {
return [pscustomobject]@{
Name = $InstalledModule.Name
Version = $InstalledModule.Version
Source = 'PowerShellGet'
}
}
return $null
}
catch {
return $null
}
}
function Get-SCA_GalleryModuleVersion {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
try {
return Find-Module -Name $Name -Repository PSGallery -ErrorAction Stop
}
catch {
Write-SCA_Status -Message "Find-Module kon $Name niet ophalen. Fallback via PSResourceGet wordt geprobeerd." -Level WARN
Write-SCA_LogFile -Message "Find-Module kon $Name niet ophalen. Fallback via PSResourceGet wordt geprobeerd." -Level WARN
try {
if (Test-SCA_CommandExists -Name 'Find-PSResource') {
$PSResource = Find-PSResource -Name $Name -Repository PSGallery -ErrorAction Stop |
Sort-Object Version -Descending |
Select-Object -First 1
if ($PSResource) {
return [pscustomobject]@{
Name = $PSResource.Name
Version = $PSResource.Version
}
}
}
}
catch {
Write-SCA_Status -Message "Fallback via PSResourceGet is mislukt voor $Name : $($_.Exception.Message)" -Level WARN
Write-SCA_LogFile -Message "Fallback via PSResourceGet is mislukt voor $Name : $($_.Exception.Message)" -Level WARN
}
return $null
}
}
function Install-SCA_Module {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[string]$RequiredVersion
)
try {
$Params = @{
Name = $Name
Repository = 'PSGallery'
Scope = 'CurrentUser'
Force = $true
AllowClobber = $true
ErrorAction = 'Stop'
}
if ($RequiredVersion) {
$Params.RequiredVersion = $RequiredVersion
}
Install-Module @Params
return $true
}
catch {
Write-SCA_Status -Message "Install-Module is mislukt voor $Name. Fallback via Install-PSResource wordt geprobeerd." -Level WARN
Write-SCA_LogFile -Message "Install-Module is mislukt voor $Name. Fallback via Install-PSResource wordt geprobeerd." -Level WARN
try {
if (Test-SCA_CommandExists -Name 'Install-PSResource') {
$PSResourceParams = @{
Name = $Name
Repository = 'PSGallery'
Scope = 'CurrentUser'
TrustRepository = $true
Force = $true
ErrorAction = 'Stop'
}
if ($RequiredVersion) {
$PSResourceParams.Version = $RequiredVersion
}
Install-PSResource @PSResourceParams
return $true
}
}
catch {
Write-SCA_Status -Message "Install-PSResource is mislukt voor $Nam : $($_.Exception.Message)" -Level ERROR
Write-SCA_LogFile -Message "Install-PSResource is mislukt voor $Name : $($_.Exception.Message)" -Level ERROR
}
return $false
}
}
function Update-SCA_Module {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
try {
Update-Module -Name $Name -Force -ErrorAction Stop
return $true
}
catch {
Write-SCA_Status -Message "Update-Module is mislukt voor $Name. Nieuwe installatie wordt geprobeerd." -Level WARN
Write-SCA_LogFile -Message "Update-Module is mislukt voor $Name. Nieuwe installatie wordt geprobeerd." -Level WARN
return (Install-SCA_Module -Name $Name)
}
}
function InstallOrUpdate-SCA_Module {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[string]$RequiredVersion,
[switch]$ForceInstall
)
Write-SCA_Status -Message "Module verwerken: $Name" -Level INFO
Write-SCA_LogFile -Message "Module verwerken: $Name" -Level INFO
$Installed = Get-SCA_InstalledModuleVersion -Name $Name
if ($RequiredVersion) {
if (-not $Installed) {
Write-SCA_Status -Message "$Name is niet geïnstalleerd. Installatie van vaste versie $RequiredVersion wordt gestart." -Level WARN
Write-SCA_LogFile -Message "$Name is niet geïnstalleerd. Installatie van vaste versie $RequiredVersion wordt gestart." -Level WARN
if (Install-SCA_Module -Name $Name -RequiredVersion $RequiredVersion) {
Write-SCA_Status -Message "$Name is succesvol geïnstalleerd op versie $RequiredVersion." -Level SUCCESS
Write-SCA_LogFile -Message "$Name is succesvol geïnstalleerd op versie $RequiredVersion." -Level SUCCESS
}
else {
Write-SCA_Status -Message "Installatie is mislukt voor $Name versie $RequiredVersion." -Level ERROR
Write-SCA_LogFile -Message "Installatie is mislukt voor $Name versie $RequiredVersion." -Level ERROR
}
return
}
Write-SCA_Status -Message "$Name lokaal: $($Installed.Version) | vereist: $RequiredVersion" -Level INFO
Write-SCA_LogFile -Message "$Name lokaal: $($Installed.Version) | vereist: $RequiredVersion" -Level INFO
if ($ForceInstall -or ([version]$Installed.Version -ne [version]$RequiredVersion)) {
Write-SCA_Status -Message "$Name wordt geïnstalleerd of gecorrigeerd naar versie $RequiredVersion." -Level WARN
Write-SCA_LogFile -Message "$Name wordt geïnstalleerd of gecorrigeerd naar versie $RequiredVersion." -Level WARN
if (Install-SCA_Module -Name $Name -RequiredVersion $RequiredVersion) {
Write-SCA_Status -Message "$Name is succesvol verwerkt op versie $RequiredVersion." -Level SUCCESS
Write-SCA_LogFile -Message "$Name is succesvol verwerkt op versie $RequiredVersion." -Level SUCCESS
}
else {
Write-SCA_Status -Message "Installatie is mislukt voor $Name versie $RequiredVersion." -Level ERROR
Write-SCA_LogFile -Message "Installatie is mislukt voor $Name versie $RequiredVersion." -Level ERROR
}
}
else {
Write-SCA_Status -Message "$Name staat al op de vereiste versie $RequiredVersion." -Level SUCCESS
Write-SCA_LogFile -Message "$Name staat al op de vereiste versie $RequiredVersion." -Level SUCCESS
}
return
}
$Gallery = Get-SCA_GalleryModuleVersion -Name $Name
if (-not $Gallery) {
Write-SCA_Status -Message "Module $Name is niet gevonden in PSGallery of kon niet worden uitgelezen." -Level ERROR
Write-SCA_LogFile -Message "Module $Name is niet gevonden in PSGallery of kon niet worden uitgelezen." -Level ERROR
return
}
if (-not $Installed) {
Write-SCA_Status -Message "$Name is niet geïnstalleerd. Installatie wordt gestart." -Level WARN
Write-SCA_LogFile -Message "$Name is niet geïnstalleerd. Installatie wordt gestart." -Level WARN
if (Install-SCA_Module -Name $Name) {
Write-SCA_Status -Message "$Name is succesvol geïnstalleerd. Versie: $($Gallery.Version)" -Level SUCCESS
Write-SCA_LogFile -Message "$Name is succesvol geïnstalleerd. Versie: $($Gallery.Version)" -Level SUCCESS
}
else {
Write-SCA_Status -Message "Installatie is mislukt voor $Name." -Level ERROR
Write-SCA_LogFile -Message "Installatie is mislukt voor $Name." -Level ERROR
}
return
}
Write-SCA_Status -Message "$Name lokaal: $($Installed.Version) | gallery: $($Gallery.Version)" -Level INFO
Write-SCA_LogFile -Message "$Name lokaal: $($Installed.Version) | gallery: $($Gallery.Version)" -Level INFO
if ($ForceInstall -or ([version]$Gallery.Version -gt [version]$Installed.Version)) {
Write-SCA_Status -Message "$Name wordt bijgewerkt." -Level WARN
Write-SCA_LogFile -Message "$Name wordt bijgewerkt." -Level WARN
if (Update-SCA_Module -Name $Name) {
Write-SCA_Status -Message "$Name is bijgewerkt naar versie $($Gallery.Version)." -Level SUCCESS
Write-SCA_LogFile -Message "$Name is bijgewerkt naar versie $($Gallery.Version)." -Level SUCCESS
}
else {
Write-SCA_Status -Message "Bijwerken is mislukt voor $Name." -Level ERROR
Write-SCA_LogFile -Message "Bijwerken is mislukt voor $Name." -Level ERROR
}
}
else {
Write-SCA_Status -Message "$Name is al actueel." -Level SUCCESS
Write-SCA_LogFile -Message "$Name is al actueel." -Level SUCCESS
}
}
function Import-SCA_SelectedModules {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[object[]]$Modules
)
foreach ($ModuleDefinition in $Modules) {
$Module = ConvertTo-SCA_ModuleObject -Module $ModuleDefinition
try {
Import-Module $Module.Name -Force -ErrorAction Stop
Write-SCA_Status -Message "$($Module.Name) is geladen in de sessie." -Level SUCCESS
Write-SCA_LogFile -Message "$($Module.Name) is geladen in de sessie." -Level SUCCESS
}
catch {
Write-SCA_Status -Message "Importeren is mislukt voor $($Module.Name): $($_.Exception.Message)" -Level ERROR
Write-SCA_LogFile -Message "Importeren is mislukt voor $($Module.Name): $($_.Exception.Message)" -Level ERROR
}
}
}
#endregion Helper functions
#region Module configuration
$ModuleSets = @{
Bootstrap = @(
@{ Name = 'PackageManagement' },
@{ Name = 'PowerShellGet' },
@{ Name = 'Microsoft.PowerShell.PSResourceGet' }
)
Production = @(
@{ Name = 'Az.Accounts' },
@{ Name = 'Microsoft.Graph' },
@{ Name = 'Microsoft.Entra' },
@{ Name = 'PnP.PowerShell' },
@{ Name = 'Microsoft.Online.SharePoint.PowerShell' },
@{ Name = 'ExchangeOnlineManagement' },
@{ Name = 'MicrosoftTeams' },
@{ Name = 'CIS-M365-Benchmark'; RequiredVersion = '5.2.0' }
)
Beta = @(
@{ Name = 'Az.Accounts' },
@{ Name = 'Microsoft.Graph.Beta' },
@{ Name = 'Microsoft.Entra.Beta' }
)
}
$SelectedModules = if ($Beta) {
$ModuleSets.Beta
}
else {
$ModuleSets.Production
}
$AllModules = @($ModuleSets.Bootstrap + $SelectedModules)
#endregion Module configuration
#region Logging header
$PublicIP = Get-SCA_PublicIpAddress
$PrivateIP = Get-SCA_PrivateIpAddress
Write-SCA_LogFile -Message "Current Date = $CurrentDate"
Write-SCA_LogFile -Message "Script Author = $ScriptAuthor"
Write-SCA_LogFile -Message "Script Version = $ScriptVersion"
Write-SCA_LogFile -Message "Script ChangeDate = $ScriptChangeDate"
Write-SCA_LogFile -Message "Script ChangeLog = $ScriptChangeLog"
Write-SCA_LogFile -Message "Current User Running this script = $ScriptCurrentUser"
Write-SCA_LogFile -Message "Current Device Running this script = $ScriptRunningDevice"
Write-SCA_LogFile -Message "Current Public IP = $PublicIP"
Write-SCA_LogFile -Message "Current Private IP = $PrivateIP"
Write-SCA_LogFile -Message "Script mode = $ScriptMode"
Write-SCA_LogFile -Message "Bootstrap modules = $((($ModuleSets.Bootstrap | ForEach-Object { (ConvertTo-SCA_ModuleObject -Module $_).Name }) -join ', '))"
Write-SCA_LogFile -Message "Selected modules = $((($SelectedModules | ForEach-Object { (ConvertTo-SCA_ModuleObject -Module $_).Name }) -join ', '))"
Write-SCA_LogFile -Message "All modules = $((($AllModules | ForEach-Object { (ConvertTo-SCA_ModuleObject -Module $_).Name }) -join ', '))"
Write-SCA_Status -Message "Scriptmodus: $ScriptMode" -Level INFO
Write-SCA_Status -Message "Bootstrapmodules: $((($ModuleSets.Bootstrap | ForEach-Object { (ConvertTo-SCA_ModuleObject -Module $_).Name }) -join ', '))" -Level INFO
Write-SCA_Status -Message "Geselecteerde modules: $((($SelectedModules | ForEach-Object { (ConvertTo-SCA_ModuleObject -Module $_).Name }) -join ', '))" -Level INFO
#endregion Logging header
#region Transcript
$TranscriptFile = "$LogDir\$CurrentDate-$LogName-Transcript.log"
$TranscriptStarted = $false
try {
Start-Transcript -Path $TranscriptFile -Force | Out-Null
$TranscriptStarted = $true
Write-SCA_LogFile -Message "Transcript gestart: $TranscriptFile" -Level SUCCESS
}
catch {
Write-SCA_LogFile -Message "Start-Transcript is mislukt: $($_.Exception.Message)" -Level ERROR
}
#endregion Transcript
#region Main
try {
Ensure-SCA_PowerShellGallery
foreach ($ModuleDefinition in $AllModules) {
$Module = ConvertTo-SCA_ModuleObject -Module $ModuleDefinition
InstallOrUpdate-SCA_Module `
-Name $Module.Name `
-RequiredVersion $Module.RequiredVersion `
-ForceInstall:$ForceInstall
}
if ($ImportModules) {
Import-SCA_SelectedModules -Modules $SelectedModules
}
Write-SCA_Status -Message "Module-initialisatie is afgerond voor modus: $ScriptMode" -Level SUCCESS
Write-SCA_LogFile -Message "Module-initialisatie is afgerond voor modus: $ScriptMode" -Level SUCCESS
}
catch {
Write-SCA_Status -Message "Het script is beëindigd met een fout: $($_.Exception.Message)" -Level ERROR
Write-SCA_LogFile -Message "Het script is beëindigd met een fout: $($_.Exception.Message)" -Level FATAL
}
finally {
if ($TranscriptStarted) {
try {
Stop-Transcript | Out-Null
Write-SCA_LogFile -Message 'Transcript gestopt.' -Level SUCCESS
}
catch {
Write-SCA_LogFile -Message "Stop-Transcript is mislukt: $($_.Exception.Message)" -Level ERROR
}
}
$IntuneLogPath = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
if ((Test-Path -Path $IntuneLogPath) -and (Test-Path -Path $TranscriptFile)) {
try {
Copy-Item -Path $TranscriptFile -Destination $IntuneLogPath -Force -ErrorAction Stop
Write-SCA_LogFile -Message 'Transcript is gekopieerd naar de Intune-logmap.' -Level SUCCESS
}
catch {
Write-SCA_LogFile -Message "Kopiëren naar de Intune-logmap is mislukt: $($_.Exception.Message)" -Level ERROR
}
}
}
#endregion Main
Conclusie
Dit script heb ik geschreven omdat ik het zat was om handmatig modules te installeren, te updaten en te herstellen als ik weer een machine opnieuw had ingericht of in een verse omgeving werkte.
Dus heb ik het proces geautomatiseerd.
Nu draait dit script telkens mee wanneer ik een ander specifiek script uitvoer, zodat mijn PowerShell-omgeving eerst netjes wordt klaargezet. Geen losse eindjes, geen ontbrekende modules en een stuk minder frustratie.
Kort gezegd:
ik heb een terugkerend probleem vervangen door een script.
Zoals het hoort.