Doorlopende Vaardigheidsontwikkeling Voor Microsoft 365 Security Operations

💼 Management Samenvatting

De kwaliteit van een Security Operations Center wordt in de Nederlandse publieke sector niet enkel bepaald door tooling, maar door de continu ontwikkelende vaardigheden van analisten, engineers en incidentleiders. Wie Microsoft 365, Defender XDR en Sentinel inzet, heeft een lerend vermogen nodig dat zich aanpast aan dreigingen, wetgeving en reorganisaties. Dit artikel beschrijft hoe u vaardigheidsontwikkeling als kritieke controlemaatregel verankert in de Nederlandse Baseline voor Veilige Cloud en hoe u bestuurders overtuigt met harde bewijzen.

Aanbeveling
PLAN_EN_IMPLEMENTEER
Risico zonder
High
Risk Score
8/10
Implementatie
180u (tech: 70u)
Van toepassing op:
Microsoft 365
Microsoft Defender XDR
Microsoft Sentinel
Microsoft Viva Learning
Microsoft Graph

Gemeenten, ministeries en uitvoeringsorganisaties staan onder druk van de BIO, AVG en NIS2 om aantoonbaar bekwaam personeel in te zetten op security operations. Traditionele trainingen voldoen niet meer nu AI-gedreven aanvallen en supply-chaincasussen binnen uren escaleren. Zonder een gedocumenteerde vaardigheidsroadmap raakt het SOC afhankelijk van enkele experts, lopen runbooks achter en ontbreekt traceerbaar bewijs richting de Algemene Rekenkamer of departementale auditdiensten. Hierdoor ontstaan bestuurlijke risico’s en wordt opschaling naar crisiscentra bemoeilijkt.

PowerShell Modules Vereist
Primary API: Microsoft Graph Users, Reports en Viva Learning API’s
Connection: Connect-MgGraph -Scopes User.Read.All, Reports.Read.All, LearningContent.Read.All -NoWelcome
Required Modules: Microsoft.Graph.Authentication, Microsoft.Graph.Users, Microsoft.Graph.Reports

Implementatie

Het artikel levert een volledig raamwerk waarin strategische besturing, operationele dataverzameling, coaching en auditverantwoording samenkomen. We refereren voortdurend aan het PowerShell-script `skills-development.ps1`, dat referentieconfiguraties opzet, vaardigheidsgaten inzichtelijk maakt en rapportages met hashwaarden produceert. De nadruk ligt op realistische scenario’s voor Nederlandse overheidsdomeinen, inclusief afspraken met ondernemingsraden, samenwerking met regionale SOC’s en het koppelen van Viva Learning-data aan risk dashboards.

Strategische horizon, governance en legitimiteit van vaardigheidsontwikkeling

Gebruik PowerShell-script skills-development.ps1 (functie Initialize-SkillsPortfolio) – Publiceert een referentieconfiguratie met rolprofielen, target-scores en goedgekeurde leerlijnen zodat audits kunnen toetsen tegen een consistente standaard..

Strategische regie over vaardigheden begint bij een bestuurlijk besluit waarin de secretaris-generaal, gemeentesecretaris of korpsleiding vastlegt dat Microsoft 365 security operations alleen worden uitgevoerd door aantoonbaar bekwame professionals. Dat besluit koppelt dreigingsbeelden van het NCSC aan de Nederlandse Baseline voor Veilige Cloud, benoemt kritieke processen zoals identity monitoring en Purview forensics en geeft het SOC expliciet budget en tijd voor ontwikkeling. Door het programma in het risicoregister op te nemen ontstaat mandaat om productiewerk tijdelijk te verminderen zodra skills onder de drempel zakken.

Governance vraagt om een portfolio-aanpak waarin rollen, skills en KPI’s gelijkwaardig worden behandeld. Iedere rol – van Tier-1-analist tot use-case architect – krijgt een eigenaar, een set verplichte scenario’s en een herzieningsinterval. Het script `Initialize-SkillsPortfolio` levert de basisconfiguratie en borgt dat alle metadata – zoals risicoclassificatie per vaardigheid en de koppeling naar BIO-paragrafen – uniform worden vastgelegd. Daardoor kan de security board ieder kwartaal toetsen of de leeragenda nog aansluit op veranderende programmadoelen, zoals de introductie van Copilot for Security of een regionale samenwerkingsovereenkomst.

Legitimiteit richting medewerkers en ondernemingsraden ontstaat door transparant uit te leggen welke persoonsgegevens het programma verwerkt en waarom. Vaardigheidsscores, examendata en labresultaten worden geminimaliseerd tot datgene wat nodig is om BIO en AVG te onderbouwen. Het artikel adviseert om deze afweging vast te leggen in DPIA’s, afspraken met HR en trainingsregelingen, zodat niemand vaardigheden verwart met prestatiebeoordelingen. Tegelijkertijd legt u vast hoe tijdelijke inhuur of ketenpartners moeten aantonen dat ze dezelfde standaarden volgen – essentieel wanneer RijksSOC en gemeentelijke SOC’s personeel uitwisselen.

Strategische planning eindigt niet bij het vastleggen van eisen; zij verplicht tot continue scenario-analyse. Door veranderanalyses voor Wet open overheid, parlementaire onderzoeken en internationale missies te koppelen aan het vaardigheidsportfolio wordt duidelijk welke skills op korte termijn schaars worden. Het artikel beschrijft hoe u hiervoor bestuurlijke KPI’s definieert, zoals “percentage SOC-runs met dubbelbezetting op crisiscoaches” of “publieke incidenten met een aangetoonde leergap als oorzaak”. Zo ontstaat een bestuurlijke dialoog waarin vaardigheidsontwikkeling dezelfde status krijgt als netwerksegmentatie of identity hardening.

Financiële en personele planning worden verankerd door vaardigheidsdoelen te koppelen aan meerjarige investeringsprogramma’s. Het portfolio bevat per capability een businesscase waarin wordt uitgelegd welke maatschappelijke waarde ontstaat wanneer teams sneller reageren op phishingcampagnes of wanneer forensische onderzoeken minder afhankelijk zijn van externe consultants. Dezelfde businesscase benoemt de kosten voor labs, certificeringen, vervangingscapaciteit en tooling voor vaardigheidsmeting. Daardoor kunnen bestuurders prioriteiten afwegen op basis van risico- en kostenreductie in plaats van buikgevoel, en schuiven vaardigheidsprojecten niet langer automatisch naar de achtergrond.

Datakwaliteit en beveiliging van vaardigheidsinformatie krijgen een even grote rol als de inhoud. Het programma specificeert encryptiestandaarden, logging en toegangscontrole voor het configuratiebestand en de rapportages die het script genereert. Slechts een beperkte set functies – zoals de SOC-manager, HR business partner en privacy officer – krijgt toegang tot naamherleidbare gegevens, terwijl bestuurders en auditors geaggregeerde informatie ontvangen. Door hashwaarden te registreren en integriteitschecks te automatiseren kan achteraf worden aangetoond dat er niet met gegevens is geknoeid, ook niet tijdens crisissituaties. Deze combinatie van governance, privacy en integriteit maakt het strategische hoofdstuk compleet.

Operationele metingen, simulaties en automatisering van vaardigheidsdata

Gebruik PowerShell-script skills-development.ps1 (functie Invoke-SkillsAssessment) – Combineert configuratiedoelen met actuele meetgegevens, rekent maturiteitsscores uit en exporteert een CSV-gap-analyse binnen vijftien seconden..

Operationele sturing vereist betrouwbare data. Het script leest referentieconfiguraties, Viva Learning-exports en optioneel Microsoft Graph Reports om te bepalen hoeveel medewerkers recent labs of examens hebben voltooid. Wanneer DebugMode actief is, gebruikt het script lokale voorbeelddata zodat controles binnen vijftien seconden kunnen worden uitgevoerd, zelfs op besloten netwerken. De uitkomst wordt weergegeven als maturiteitsscore per rol, inclusief kans op single points of failure en adviezen voor vervangingscapaciteit. Daarmee krijgt het SOC inzicht in welke teams direct bijgeschoold moeten worden voordat nieuwe functies zoals Adaptive Protection worden uitgerold.

Simulaties zijn het hart van de ontwikkelcyclus. Het artikel beschrijft hoe u maandelijks labs plant waarin analisten live KQL-queries schrijven, runbooks bijwerken en Purview-onderzoeken oefenen, terwijl engineers sentinel-analyses automatiseren. De resultaten worden direct weggeschreven naar het configuratiebestand, waardoor `Invoke-SkillsAssessment` kan berekenen hoe lang het geleden is dat een specifieke vaardigheid is getest. Dit voorkomt dat oefeningen incidenteel gebeuren en zorgt er voor dat lessons learned uit incidenten onmiddellijk worden vertaald naar een nieuwe simulatieronde.

Automatisering vermindert handwerk en maakt rapportages reproduceerbaar. Het script controleert of de CSV- en JSON-bronbestanden cryptografisch zijn gehasht, zodat auditors kunnen zien dat data niet is gemanipuleerd. Wanneer Graph-toegang beschikbaar is, haalt het script gegevens op over certificeringsverlopen, licentie-inzet of deelname aan Attack Simulation Training. Bij uitval van clouddiensten schakelt het automatisch terug naar lokale telemetry, zodat analyses doorgaan zonder afhankelijkheid van internetverbindingen. Dit sluit aan bij de eis van de Nederlandse overheid om kritieke controles ook in gescheiden omgevingen te kunnen draaien.

De operationele laag omvat tevens feedback vanuit SOC-shifts, red-teams en leveranciers. Door het configuratiebestand te verrijken met kwalitatieve observaties – denk aan opmerkingen over tooling, governance of samenwerking met RijksSOC – ontstaat een compleet beeld dat verder gaat dan statistiek. Het artikel laat zien hoe u deze observaties structureert, bijvoorbeeld door incidentcodes toe te voegen of door verwijzingen naar verbeteracties in het GRC-systeem op te nemen. Zo bouwt u een gezamenlijke taal tussen techniek, HR en bestuurders.

Instrumentation van leerdata wordt bewust redundant opgezet. Naast Graph-exports kan het script periodiek CSV-bestanden lezen uit LMS-systemen of gesigneerde Excel-rapporten uit besloten netwerken. Elke bron krijgt een betrouwbaarheidsscore in het configuratiebestand, zodat automatisch zichtbaar is welke dataset leidend is wanneer cijfers botsen. Hierdoor ontstaat geen discussie meer tussen HR, security en leveranciers over welke cijfers kloppen; de rekenkern hanteert een vooraf goedgekeurde hiërarchie en logt iedere keuze.

Kennisborging krijgt eveneens aandacht. De resultaten van labs en assessments worden direct gekoppeld aan runbooks, threat-hunting packs en knowledge articles. Wie een oefening afrondt, documenteert welke queries, scripts of dashboards zijn verbeterd en verwijst naar het betreffende Git- of Purview-record. Zo blijft tacit knowledge niet hangen bij individuele medewerkers maar vloeit terug naar de standaarddocumentatie. Bij uitval van personeel of tijdens crisisschaling kan een ander teamlid daardoor dezelfde informatie gebruiken zonder kwaliteitsverlies.

Ook leveranciers en ketenpartners vallen binnen de operationele scope. Het configuratiebestand bevat per externe partij welke kennis zij leveren, hoe vaak die kennis wordt getoetst en welke fallback beschikbaar is als een contract eindigt. Het script vergelijkt deze afspraken met de realiteit door te controleren of gezamenlijke labs werkelijk zijn uitgevoerd en of toegang tot kennisportalen actueel is. Zo ontstaat een compleet beeld van de totale vaardighedenketen, inclusief uitbestede werkzaamheden.

Rapportageketen, coaching en auditklare bewijsvoering

Gebruik PowerShell-script skills-development.ps1 (functie Publish-SkillsEvidence) – Maakt een managementrapport met risicoscores, hashwaarden en exporteert coachingsacties naar een CSV die rechtstreeks in GRC- en HR-systemen kan worden geladen..

Rapportage binnen de overheid draait om betrouwbaarheid en herleidbaarheid. `Publish-SkillsEvidence` produceert een JSON-rapport met samengevatte KPI’s per proces, voorzien van een SHA256-hash en metadata over de gebruikte bronbestanden. Hierdoor kan de auditdienst later exact vaststellen welke dataset is gebruikt bij een specifieke bestuursrapportage. Het artikel beschrijft hoe u deze rapporten koppelt aan Purview Records of het departementale archief, inclusief retentietermijnen en classificaties volgens het Rijksbreed labelstelsel.

Coaching krijgt een eigen paragraaf omdat vaardigheidsontwikkeling niet stopt na een meting. Managers ontvangen stuurkaarten met concrete interventies, zoals het plannen van duo-shifts tussen ervaren en junior analisten of het inzetten van scenario coaches die crisissimulaties begeleiden. Deze interventies worden geregistreerd in hetzelfde configuratiebestand, zodat bestuurders kunnen volgen of afspraken daadwerkelijk zijn uitgevoerd. Het artikel benadrukt dat coaching nadrukkelijk geen HR-dossier is maar een securitymaatregel die direct invloed heeft op responstijden en incidentkwaliteit.

Auditklare bewijsvoering vraagt om traceerbaarheid tussen beleid, metingen en acties. Daarom beschrijven we hoe u elke rapportage voorziet van verwijzingen naar beleidsdocumenten, DPIA’s, change requests en lessons learned. Wanneer auditors of parlementaire commissies vragen stellen, kan het SOC in minuten aantonen welke opleidingen zijn gevolgd en welke verbeteringen gepland staan. Dit voorkomt reputatieschade bij publieke informatieverzoeken en laat zien dat de organisatie proactief leert van incidenten.

Tot slot onderstreept het artikel de noodzaak van ketensamenwerking. Regionale SOC’s, IBD en private partners delen dezelfde rapportagesjablonen, zodat gezamenlijke oefeningen en crisisopschaling soepel verlopen. Door expliciet te documenteren welke partners toegang hebben tot rapportages en hoe gegevens worden geanonimiseerd, blijft de AVG geborgd terwijl ketenpartners toch inzicht krijgen in de vaardigheidsstatus. Zo wordt duidelijk dat vaardigheidsontwikkeling geen intern project is maar een publieke verantwoordelijkheid.

Coachinginformatie wordt bovendien ontsloten via digitale kanalen. Het script kan een CSV publiceren voor Viva Goals of Power BI Goals, waardoor teamleiders real-time zien welke coachingafspraken openstaan, wie mentor is en wanneer de volgende evaluatie plaatsvindt. Hierdoor sluit vaardighedenontwikkeling aan op dezelfde OKR-cyclus die al voor technische projecten wordt gebruikt en zien bestuurders in één dashboard hoe techniek, processen en mensen zich ontwikkelen.

Rapportages worden eveneens ingezet tijdens parlementaire enquêtes of Woo-procedures. Omdat elke dataset een hash heeft en het rapport aangeeft welke persoonsgegevens zijn geanonimiseerd, kan het bestuur gecontroleerd informatie delen zonder de privacy van medewerkers te schenden. De combinatie van juridische onderbouwing, technische integriteit en bestuurlijke transparantie maakt de rapportageketen volwassen en auditproof.

Culturele verankering blijft essentieel. Het artikel beschrijft hoe u resultaten deelt in townhalls, brown bag-sessies en security communities of practice, zodat medewerkers begrijpen waarom bepaalde coachingacties prioriteit krijgen. Door successen zichtbaar te maken – bijvoorbeeld het terugdringen van escalaties na een reeks mentorsessies – ontstaat een positieve leercultuur die talent behoudt en nieuwe collega’s aantrekt.

Ten slotte introduceren we prestatie-indicatoren die rapportages direct koppelen aan dienstverlening. Denk aan correlaties tussen skill-scores en mean time to contain, of tussen coachingdichtheid en het aantal herhaalde incidenten. Deze analyses tonen bestuurders dat investeren in vaardigheden daadwerkelijk leidt tot betere beveiligingsresultaten en geven auditors een objectief kader om doeltreffendheid te toetsen.

Het hoofdstuk sluit af met concrete archiveringsadviezen: registreer hashwaarden in Purview Records, noteer welke versies met bestuurders zijn gedeeld en leg vast welke HR- of OR-documenten als bijlage meegaan. Hierdoor vormt elke rapportage een compleet dossier dat moeiteloos opnieuw kan worden opgebouwd tijdens audits of gerechtelijke onderzoeken.

Compliance & Frameworks

Automation

Gebruik het onderstaande PowerShell script om deze security control te monitoren en te implementeren. Het script bevat functies voor zowel monitoring (-Monitoring) als remediation (-Remediation).

PowerShell
<# .SYNOPSIS Vaardighedenontwikkeling voor Microsoft 365 security operations. .DESCRIPTION Dit script ondersteunt CISO- en SOC-teams bij het vastleggen van vaardigheidsdoelen, het monitoren van dekking en het genereren van auditklare rapportages. Het werkt volledig met lokale configuratie- bestanden zodat het veilig inzetbaar is in gescheiden overheidsnetwerken. .NOTES Project : Nederlandse Baseline voor Veilige Cloud Author : Nederlandse Baseline voor Veilige Cloud Team File : skills-development.ps1 .EXAMPLE .\skills-development.ps1 -Baseline Maakt een basisconfiguratie of werkt deze bij inclusief back-up. .EXAMPLE .\skills-development.ps1 -Assess Toont dekking per vaardigheid en signaleert trainingsachterstanden. .EXAMPLE .\skills-development.ps1 -Export Exporteert een skill-matrix naar CSV voor dashboards of audits. .EXAMPLE .\skills-development.ps1 -Report Genereert een managementrapport inclusief hashwaarde voor bewijsvoering. #> #Requires -Version 5.1 [CmdletBinding(DefaultParameterSetName = 'Help')] param( [Parameter(ParameterSetName = 'Baseline')] [switch]$Baseline, [Parameter(ParameterSetName = 'Assess')] [switch]$Assess, [Parameter(ParameterSetName = 'Export')] [switch]$Export, [Parameter(ParameterSetName = 'Report')] [switch]$Report, [Parameter(ParameterSetName = 'Revert')] [switch]$Revert, [Parameter(Mandatory = $false)] [switch]$WhatIf ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' Write-Host "`n===============================================" -ForegroundColor Cyan Write-Host "Vaardighedenontwikkeling Microsoft 365 SOC" -ForegroundColor Cyan Write-Host "Nederlandse Baseline voor Veilige Cloud" -ForegroundColor Cyan Write-Host "===============================================`n" -ForegroundColor Cyan function Get-ConfigPath { $scriptDir = Split-Path -Parent $PSCommandPath return (Join-Path -Path $scriptDir -ChildPath 'skills-development.config.json') } function Get-MatrixPath { $scriptDir = Split-Path -Parent $PSCommandPath return (Join-Path -Path $scriptDir -ChildPath 'skills-development-matrix.csv') } function Get-ReportPath { $scriptDir = Split-Path -Parent $PSCommandPath return (Join-Path -Path $scriptDir -ChildPath 'skills-development-report.txt') } function Initialize-DefaultConfig { $today = (Get-Date).ToString('yyyy-MM-dd') $config = [ordered]@{ version = '1.0' lastUpdated = $today owner = 'SOC Skills Lead' targets = [ordered]@{ reviewIntervalDays = 90 trainingLeadTimeDays = 30 minCoveragePct = 150 minDualCoverageSkills = 2 } skills = @( [ordered]@{ id = 'SKL-001' name = 'KQL-detectieregels' capability = 'Detectie' owner = 'Use-case engineer lead' requiredCoverage = 4 minProficiency = 4 lastReview = (Get-Date).AddDays(-45).ToString('yyyy-MM-dd') }, [ordered]@{ id = 'SKL-002' name = 'Automatisering met PowerShell en Logic Apps' capability = 'Automatisering' owner = 'Automation architect' requiredCoverage = 3 minProficiency = 3 lastReview = (Get-Date).AddDays(-70).ToString('yyyy-MM-dd') }, [ordered]@{ id = 'SKL-003' name = 'Crisiscommunicatie en bridge-coordinatie' capability = 'Respons' owner = 'Incident commander' requiredCoverage = 2 minProficiency = 3 lastReview = (Get-Date).AddDays(-120).ToString('yyyy-MM-dd') } ) personnel = @( [ordered]@{ name = 'Anja de Vries' role = 'SOC-analist' hoursPerWeek = 36 lastAssessment = (Get-Date).AddDays(-40).ToString('yyyy-MM-dd') skills = @( [ordered]@{ skillId = 'SKL-001' level = 4 lastAssessment = (Get-Date).AddDays(-40).ToString('yyyy-MM-dd') }, [ordered]@{ skillId = 'SKL-002' level = 3 lastAssessment = (Get-Date).AddDays(-55).ToString('yyyy-MM-dd') } ) }, [ordered]@{ name = 'Jamal Smit' role = 'Use-case engineer' hoursPerWeek = 32 lastAssessment = (Get-Date).AddDays(-85).ToString('yyyy-MM-dd') skills = @( [ordered]@{ skillId = 'SKL-001' level = 5 lastAssessment = (Get-Date).AddDays(-85).ToString('yyyy-MM-dd') }, [ordered]@{ skillId = 'SKL-002' level = 4 lastAssessment = (Get-Date).AddDays(-60).ToString('yyyy-MM-dd') } ) }, [ordered]@{ name = 'Mila Otten' role = 'Incident commander' hoursPerWeek = 40 lastAssessment = (Get-Date).AddDays(-130).ToString('yyyy-MM-dd') skills = @( [ordered]@{ skillId = 'SKL-003' level = 3 lastAssessment = (Get-Date).AddDays(-130).ToString('yyyy-MM-dd') } ) } ) trainings = @( [ordered]@{ id = 'TRN-101' title = 'Advanced Sentinel Query Lab' skillId = 'SKL-001' participant = 'Mila Otten' dueDate = (Get-Date).AddDays(20).ToString('yyyy-MM-dd') status = 'Planned' }, [ordered]@{ id = 'TRN-205' title = 'Bridge-call coaching traject' skillId = 'SKL-003' participant = 'Jamal Smit' dueDate = (Get-Date).AddDays(10).ToString('yyyy-MM-dd') status = 'Scheduled' } ) } return $config } function Save-Config { param( [Parameter(Mandatory = $true)] [hashtable]$Config ) $configPath = Get-ConfigPath $configJson = $Config | ConvertTo-Json -Depth 6 if ($WhatIf) { Write-Host "[WhatIf] Configuratie zou worden geschreven naar $configPath" -ForegroundColor Yellow Write-Host $configJson return } $configDir = Split-Path -Parent $configPath if (-not (Test-Path -Path $configDir)) { New-Item -ItemType Directory -Path $configDir -Force | Out-Null } $configJson | Out-File -FilePath $configPath -Encoding UTF8 -Force Write-Host "[OK] Configuratie opgeslagen op $configPath" -ForegroundColor Green } function Get-ConfigData { $configPath = Get-ConfigPath if (-not (Test-Path -Path $configPath)) { throw "Geen configuratiebestand gevonden. Voer '.\skills-development.ps1 -Baseline' uit om een basisconfiguratie aan te maken." } $raw = Get-Content -Path $configPath -Raw return ($raw | ConvertFrom-Json -Depth 6) } function Measure-SkillCoverage { param( [Parameter(Mandatory = $true)] $Config ) $lookup = @{} foreach ($skill in $Config.skills) { $lookup[$skill.id] = $skill } $coverage = foreach ($skill in $Config.skills) { $covered = 0 foreach ($person in $Config.personnel) { $match = $person.skills | Where-Object { $_.skillId -eq $skill.id -and $_.level -ge $skill.minProficiency } if ($match) { $covered++ } } $pct = if ($skill.requiredCoverage -gt 0) { [math]::Round(($covered / $skill.requiredCoverage) * 100, 0) } else { 0 } $lastReview = if ([string]::IsNullOrWhiteSpace($skill.lastReview)) { $null } else { [datetime]::Parse($skill.lastReview) } $reviewAge = if ($lastReview) { (New-TimeSpan -Start $lastReview -End (Get-Date)).Days } else { $null } [pscustomobject]@{ SkillId = $skill.id Skill = $skill.name Capability = $skill.capability Owner = $skill.owner RequiredCoverage = $skill.requiredCoverage Covered = $covered CoveragePct = $pct MinProficiency = $skill.minProficiency LastReviewDays = $reviewAge } } return @{ Coverage = $coverage SkillIndex = $lookup } } function Get-TrainingAlerts { param( [Parameter(Mandatory = $true)] $Config ) $today = Get-Date $window = $Config.targets.trainingLeadTimeDays return $Config.trainings | Where-Object { $_.status -ne 'Completed' -and (-not [string]::IsNullOrWhiteSpace($_.dueDate)) -and ([datetime]::Parse($_.dueDate) -le $today.AddDays($window)) } } function Invoke-SkillsBaseline { try { $configPath = Get-ConfigPath if (Test-Path -Path $configPath) { $backup = "$configPath.bak_{0}" -f (Get-Date -Format 'yyyyMMddHHmmss') if (-not $WhatIf) { Copy-Item -Path $configPath -Destination $backup -Force } Write-Host "[INFO] Back-up opgeslagen als $backup" -ForegroundColor Cyan } $config = Initialize-DefaultConfig Save-Config -Config $config } catch { Write-Host "[FAIL] Baseline mislukt: $($_.Exception.Message)" -ForegroundColor Red exit 2 } } function Invoke-SkillsAssessment { try { $config = Get-ConfigData $result = Measure-SkillCoverage -Config $config $coverage = $result.Coverage Write-Host "Dekketingsanalyse per vaardigheid:" -ForegroundColor Cyan $coverage | Sort-Object Skill | Format-Table Skill, Capability, Covered, RequiredCoverage, CoveragePct, MinProficiency -AutoSize $insufficient = $coverage | Where-Object { $_.CoveragePct -lt 100 } if ($insufficient) { Write-Host "`n[WARN] Onvoldoende dekking:" -ForegroundColor Yellow foreach ($item in $insufficient) { Write-Host (" - {0}: {1}/{2} professionals ({3}% dekking)" -f $item.Skill, $item.Covered, $item.RequiredCoverage, $item.CoveragePct) -ForegroundColor Yellow } } else { Write-Host "`n[OK] Alle vaardigheden hebben voldoende dekking." -ForegroundColor Green } $staleSkills = $coverage | Where-Object { ($null -eq $_.LastReviewDays) -or ($_.LastReviewDays -gt $config.targets.reviewIntervalDays) } if ($staleSkills) { Write-Host ("`n[WARN] Skills zonder recente review (> {0} dagen):" -f $config.targets.reviewIntervalDays) -ForegroundColor Yellow foreach ($item in $staleSkills) { $age = if ($null -eq $item.LastReviewDays) { "geen review geregistreerd" } else { "{0} dagen" -f $item.LastReviewDays } Write-Host (" - {0}: {1}" -f $item.Skill, $age) -ForegroundColor Yellow } } else { Write-Host ("`n[OK] Alle skills zijn binnen {0} dagen herzien." -f $config.targets.reviewIntervalDays) -ForegroundColor Green } $stalePersonnel = foreach ($person in $config.personnel) { if ([string]::IsNullOrWhiteSpace($person.lastAssessment)) { [pscustomobject]@{ Name = $person.name Days = $null } continue } $age = (New-TimeSpan -Start ([datetime]::Parse($person.lastAssessment)) -End (Get-Date)).Days if ($age -gt $config.targets.reviewIntervalDays) { [pscustomobject]@{ Name = $person.name Days = $age } } } if ($stalePersonnel) { Write-Host ("`n[WARN] Medewerkers zonder recente assessment (> {0} dagen):" -f $config.targets.reviewIntervalDays) -ForegroundColor Yellow foreach ($item in $stalePersonnel) { $description = if ($null -eq $item.Days) { "geen assessment geregistreerd" } else { "{0} dagen" -f $item.Days } Write-Host (" - {0}: {1}" -f $item.Name, $description) -ForegroundColor Yellow } } $trainingAlerts = Get-TrainingAlerts -Config $config if ($trainingAlerts) { Write-Host ("`n[WARN] Trainingen met deadline binnen {0} dagen:" -f $config.targets.trainingLeadTimeDays) -ForegroundColor Yellow foreach ($training in $trainingAlerts) { Write-Host (" - {0} voor {1} (skill {2}) -> vervaldatum {3}" -f $training.title, $training.participant, $training.skillId, $training.dueDate) -ForegroundColor Yellow } } else { Write-Host ("`n[OK] Geen trainingen die binnen {0} dagen vervallen." -f $config.targets.trainingLeadTimeDays) -ForegroundColor Green } } catch { Write-Host "[FAIL] Assessment mislukt: $($_.Exception.Message)" -ForegroundColor Red exit 2 } } function Export-SkillMatrix { try { $config = Get-ConfigData $result = Measure-SkillCoverage -Config $config $skillIndex = $result.SkillIndex $rows = foreach ($person in $config.personnel) { foreach ($skill in $person.skills) { $meta = $skillIndex[$skill.skillId] $lastAssessment = if ([string]::IsNullOrWhiteSpace($skill.lastAssessment)) { $person.lastAssessment } else { $skill.lastAssessment } $age = if (-not [string]::IsNullOrWhiteSpace($lastAssessment)) { (New-TimeSpan -Start ([datetime]::Parse($lastAssessment)) -End (Get-Date)).Days } else { $null } [pscustomobject]@{ Name = $person.name Role = $person.role Skill = $meta.name Capability = $meta.capability Level = $skill.level TargetLevel = $meta.minProficiency LastAssessment = $lastAssessment DaysSinceReview = $age RequiredCoverage = $meta.requiredCoverage } } } if ($WhatIf) { Write-Host "[WhatIf] Skill-matrix zou worden geexporteerd naar $(Get-MatrixPath)" -ForegroundColor Yellow $rows | Format-Table -AutoSize return } $rows | Export-Csv -Path (Get-MatrixPath) -Encoding UTF8 -NoTypeInformation Write-Host "[OK] Skill-matrix opgeslagen op $(Get-MatrixPath)" -ForegroundColor Green } catch { Write-Host "[FAIL] Export mislukt: $($_.Exception.Message)" -ForegroundColor Red exit 2 } } function Publish-SkillsReport { try { $config = Get-ConfigData $result = Measure-SkillCoverage -Config $config $coverage = $result.Coverage $trainingAlerts = Get-TrainingAlerts -Config $config $lines = @() $lines += "Rapport: Vaardighedenontwikkeling Microsoft 365 SOC" $lines += "Datum : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" $lines += "Eigenaar: $($config.owner)" $lines += "" $lines += "Overzicht dekking:" foreach ($item in $coverage) { $lines += ("- {0} ({1}) -> {2}/{3} professionals ({4}% dekking)" -f $item.Skill, $item.Capability, $item.Covered, $item.RequiredCoverage, $item.CoveragePct) } $lines += "" $lines += "Trainingen binnen {0} dagen:" -f $config.targets.trainingLeadTimeDays if ($trainingAlerts) { foreach ($training in $trainingAlerts) { $lines += ("- {0} voor {1} (skill {2}) -> {3}" -f $training.title, $training.participant, $training.skillId, $training.dueDate) } } else { $lines += "- Geen openstaande trainingen binnen de drempel" } $reportPath = Get-ReportPath if ($WhatIf) { Write-Host "[WhatIf] Rapport zou worden opgeslagen op $reportPath" -ForegroundColor Yellow $lines | ForEach-Object { Write-Host $_ } return } $lines | Out-File -FilePath $reportPath -Encoding UTF8 -Force $hash = Get-FileHash -Algorithm SHA256 -Path $reportPath Write-Host "[OK] Rapport opgeslagen op $reportPath" -ForegroundColor Green Write-Host "[INFO] SHA256: $($hash.Hash)" -ForegroundColor Cyan } catch { Write-Host "[FAIL] Rapportage mislukt: $($_.Exception.Message)" -ForegroundColor Red exit 2 } } function Invoke-Revert { try { $paths = @( (Get-ConfigPath) (Get-MatrixPath) (Get-ReportPath) ) foreach ($path in $paths) { if (Test-Path -Path $path) { if ($WhatIf) { Write-Host "[WhatIf] Bestand zou worden verwijderd: $path" -ForegroundColor Yellow } else { Remove-Item -Path $path -Force Write-Host "[OK] Bestand verwijderd: $path" -ForegroundColor Green } } } } catch { Write-Host "[FAIL] Revert mislukt: $($_.Exception.Message)" -ForegroundColor Red exit 2 } } try { switch ($PSCmdlet.ParameterSetName) { 'Baseline' { Invoke-SkillsBaseline; break } 'Assess' { Invoke-SkillsAssessment; break } 'Export' { Export-SkillMatrix; break } 'Report' { Publish-SkillsReport; break } 'Revert' { Invoke-Revert; break } default { Write-Host "Gebruik:" -ForegroundColor Yellow Write-Host " -Baseline Maak of update de skills-configuratie" -ForegroundColor Gray Write-Host " -Assess Meet dekking en signaleer trainingsgaten" -ForegroundColor Gray Write-Host " -Export Exporteer de skill-matrix naar CSV" -ForegroundColor Gray Write-Host " -Report Genereer een managementrapport" -ForegroundColor Gray Write-Host " -Revert Verwijder configuratie, matrix en rapporten (optioneel -WhatIf)" -ForegroundColor Gray } } } catch { Write-Host "[FAIL] Onverwachte fout: $($_.Exception.Message)" -ForegroundColor Red exit 2 } finally { Write-Host "`n===============================================`n" -ForegroundColor Cyan }

Risico zonder implementatie

Risico zonder implementatie
High: Zonder centraal programma ontstaat een ongedocumenteerd vaardigheidstekort waardoor Microsoft 365 security operations personeel verliest, incidenten vertraagt en audits geen bewijs krijgen van organisatorische maatregelen.

Management Samenvatting

Veranker vaardigheidsontwikkeling bestuurlijk, meet maturiteit met `skills-development.ps1`, combineer Graph-data met lokale telemetry en archiveer rapportages met hashwaarden zodat bestuurders en auditors real-time inzicht krijgen in de bekwaamheid van het SOC.