tools/New-DemoEngagement.ps1
|
<# .SYNOPSIS Generates a synthetic multi-server "demo fleet" - no real server required - so anyone evaluating this toolkit can see real report output (single-server reports, a fleet rollup, the client-safe summary, the dependency diagram, the readiness score, and optionally a drift comparison) without pointing it at actual infrastructure. .DESCRIPTION Drives the exact same real pipeline tests\Invoke-OutputSmokeTest.ps1 already proves works (collection is skipped - synthetic datasets are seeded directly - but every synthesis module, every report writer, and the archive step are the real ones), once per canned "persona" (a Domain Controller, a SQL Server, a File/Print server, an aging near-EOL server, a Hyper-V host), each with richer/more varied data than the smoke test's single-row-per-dataset fixtures so the result actually looks populated rather than looking like a regression-test fixture. Each persona's run folder is written as a real, complete Discover-WindowsServer_<ComputerName>_<timestamp>\ folder - not a temp folder that gets cleaned up - so tools\Merge-FleetDiscoveryResults.ps1 and tools\Compare-DiscoveryRuns.ps1 can operate on it completely unmodified, with zero awareness the data is synthetic. With -TwoSnapshots, a second engagement folder is generated with small, deliberate, reproducible differences per persona (a share added, a service's account changed, a new scheduled task) so Compare-DiscoveryRuns.ps1's drift detection has real drift to show off too - not randomized, so the demo is reproducible run to run. .PARAMETER OutputFolder Where the demo engagement folder(s) are written. .PARAMETER Personas Which personas to generate. Defaults to all five. .PARAMETER ProjectType -ProjectType passed through to every persona's discovery context (affects which findings are emphasised). Defaults to GeneralDiscovery (no emphasis bias) so the demo doesn't look tuned toward one particular kind of engagement. .PARAMETER TwoSnapshots Also generates a second, slightly-different engagement folder (named with a later timestamp) so tools\Compare-DiscoveryRuns.ps1 has something real to compare. .EXAMPLE .\tools\New-DemoEngagement.ps1 -OutputFolder C:\Temp\Demo .EXAMPLE .\tools\New-DemoEngagement.ps1 -OutputFolder C:\Temp\Demo -TwoSnapshots # then: # .\tools\Merge-FleetDiscoveryResults.ps1 -EngagementFolder <the newer Fleet_* folder> # .\tools\Compare-DiscoveryRuns.ps1 -BaselineEngagementFolder <older> -CurrentEngagementFolder <newer> #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$OutputFolder, [ValidateSet('DomainController', 'SqlServer', 'FilePrintServer', 'AgingNearEol', 'HyperVHost')] [string[]]$Personas = @('DomainController', 'SqlServer', 'FilePrintServer', 'AgingNearEol', 'HyperVHost'), [string]$ProjectType = 'GeneralDiscovery', [switch]$TwoSnapshots ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot #region Persona fixtures --------------------------------------------------------- function Get-DemoServerPersona { <# Pure: returns one persona's synthetic dataset rows (field names/shapes match the real collector schemas - confirmed against tests\Invoke-OutputSmokeTest.ps1's own proven fixtures, just with more rows and more variety per dataset so a demo actually looks populated). -Overrides replaces specific dataset names wholesale (used for the -TwoSnapshots drift pass) rather than merging field-by-field - keeps the diff concrete and easy to reason about. #> param( [Parameter(Mandatory)][string]$PersonaName, [hashtable]$Overrides = @{} ) $computerName = switch ($PersonaName) { 'DomainController' { 'DEMO-DC01' } 'SqlServer' { 'DEMO-SQL01' } 'FilePrintServer' { 'DEMO-FS01' } 'AgingNearEol' { 'DEMO-LEGACY01' } 'HyperVHost' { 'DEMO-HV01' } } $datasets = [ordered]@{} $edges = [System.Collections.Generic.List[object]]::new() switch ($PersonaName) { 'DomainController' { $datasets['ExecutionContext'] = @([pscustomobject]@{ IsAdmin = $true }) $datasets['OperatingSystem'] = @([pscustomobject]@{ Caption = 'Windows Server 2019 Standard'; BuildNumber = '17763'; IsEndOfLifeOrNear = $false }) $datasets['PendingReboot'] = @([pscustomobject]@{ RebootPending = $false }) $datasets['RolesFeatures'] = @( [pscustomobject]@{ Name = 'AD-Domain-Services'; DisplayName = 'Active Directory Domain Services'; InstallState = 'Installed' } [pscustomobject]@{ Name = 'DNS'; DisplayName = 'DNS Server'; InstallState = 'Installed' } [pscustomobject]@{ Name = 'DHCP'; DisplayName = 'DHCP Server'; InstallState = 'Installed' } ) $datasets['DomainContext'] = @([pscustomobject]@{ IsDomainController = $true; HoldsFsmoRole = $true }) $datasets['Services'] = @( [pscustomobject]@{ Name = 'NTDS'; DisplayName = 'Active Directory Domain Services'; StartName = 'LocalSystem'; PathName = 'C:\Windows\System32\ntdsa.dll'; PathExists = $true; UnquotedPathWithSpaces = $false; IsNonMicrosoftAutoStart = $false } [pscustomobject]@{ Name = 'DNS'; DisplayName = 'DNS Server'; StartName = 'LocalSystem'; PathName = 'C:\Windows\System32\dns.exe'; PathExists = $true; UnquotedPathWithSpaces = $false; IsNonMicrosoftAutoStart = $false } ) $datasets['ScheduledTasks'] = @( [pscustomobject]@{ TaskName = 'ADBackup'; Principal = 'CORP\svc-adbackup'; ActionsText = 'wbadmin start systemstatebackup'; LastTaskResult = 0; UsesUncPath = $false; LastRunFailed = $false; RunsAsDomainAccount = $true } ) $datasets['SecurityPosture'] = @([pscustomobject]@{ Smb1Enabled = $false; RdpEnabledNoNla = $false; AnyAvOrEdrDetected = $true; LocalAdminCount = 3 }) $datasets['Volumes'] = @([pscustomobject]@{ DriveLetter = 'C'; PercentFree = 42; FreeGB = 210; SizeGB = 500 }) $datasets['IPConfiguration'] = @([pscustomobject]@{ InterfaceAlias = 'Ethernet'; IPv4Address = '10.0.0.10'; IsStatic = $true }) $datasets['Licensing'] = @([pscustomobject]@{ Indicator = 'Windows Server Standard - 16 core license'; Evidence = 'registry' }) $edges.Add((New-DemoEdge 'Server' $computerName 'DNS' '10.0.0.10' 'IPConfiguration')) $edges.Add((New-DemoEdge 'ScheduledTask' 'ADBackup' 'RunsAs' 'CORP\svc-adbackup' 'ScheduledTasks')) } 'SqlServer' { $datasets['ExecutionContext'] = @([pscustomobject]@{ IsAdmin = $true }) $datasets['OperatingSystem'] = @([pscustomobject]@{ Caption = 'Windows Server 2019 Standard'; BuildNumber = '17763'; IsEndOfLifeOrNear = $false }) $datasets['PendingReboot'] = @([pscustomobject]@{ RebootPending = $false }) $datasets['SqlInstances'] = @( [pscustomobject]@{ InstanceName = 'DEMO-SQL01\PROD'; ServiceName = 'MSSQL$PROD'; DeepQueryPerformed = $false; BinaryPath = 'C:\Program Files\Microsoft SQL Server\MSSQL15.PROD\MSSQL\Binn' } [pscustomobject]@{ InstanceName = 'DEMO-SQL01\REPORTING'; ServiceName = 'MSSQL$REPORTING'; DeepQueryPerformed = $false; BinaryPath = 'C:\Program Files\Microsoft SQL Server\MSSQL15.REPORTING\MSSQL\Binn' } ) $datasets['OdbcDsns'] = @( [pscustomobject]@{ DsnName = 'ERP'; Server = 'DEMO-SQL01\PROD'; Database = 'erp'; Driver = 'SQL Server' } [pscustomobject]@{ DsnName = 'Reporting'; Server = 'DEMO-SQL01\REPORTING'; Database = 'reporting'; Driver = 'SQL Server' } ) $datasets['Services'] = @( [pscustomobject]@{ Name = 'MSSQL$PROD'; DisplayName = 'SQL Server (PROD)'; StartName = 'CORP\svc-sqlprod'; PathName = 'C:\Program Files\Microsoft SQL Server\MSSQL15.PROD\MSSQL\Binn\sqlservr.exe'; PathExists = $true; UnquotedPathWithSpaces = $false; IsNonMicrosoftAutoStart = $false } ) $datasets['ListeningPorts'] = @([pscustomobject]@{ LocalPort = 1433; Process = 'sqlservr'; ServiceName = 'MSSQL$PROD' }) $datasets['SecurityPosture'] = @([pscustomobject]@{ Smb1Enabled = $false; RdpEnabledNoNla = $false; AnyAvOrEdrDetected = $true; LocalAdminCount = 4 }) $datasets['Certificates'] = @([pscustomobject]@{ Subject = 'CN=demo-sql01.corp.local'; NotAfter = '2026-11-15'; Thumbprint = 'AB12CD34'; ExpiringSoon = $true; HasPrivateKey = $true }) $datasets['Volumes'] = @([pscustomobject]@{ DriveLetter = 'D'; PercentFree = 18; FreeGB = 180; SizeGB = 1000 }) $datasets['Licensing'] = @([pscustomobject]@{ Indicator = 'SQL Server Standard - core-based licensing'; Evidence = 'registry' }) $edges.Add((New-DemoEdge 'SQL Instance' 'DEMO-SQL01\PROD' 'RunsAs' 'CORP\svc-sqlprod' 'Services')) $edges.Add((New-DemoEdge 'ODBC DSN' 'ERP' 'ConnectsTo' 'DEMO-SQL01\PROD' 'OdbcDsns')) } 'FilePrintServer' { $datasets['ExecutionContext'] = @([pscustomobject]@{ IsAdmin = $true }) $datasets['OperatingSystem'] = @([pscustomobject]@{ Caption = 'Windows Server 2022 Standard'; BuildNumber = '20348'; IsEndOfLifeOrNear = $false }) $datasets['PendingReboot'] = @([pscustomobject]@{ RebootPending = $false }) $shareRows = @( [pscustomobject]@{ Name = 'Finance'; Path = 'D:\Shares\Finance'; IsUserShare = $true } [pscustomobject]@{ Name = 'HR'; Path = 'D:\Shares\HR'; IsUserShare = $true } [pscustomobject]@{ Name = 'Public'; Path = 'D:\Shares\Public'; IsUserShare = $true } [pscustomobject]@{ Name = 'Engineering'; Path = 'D:\Shares\Engineering'; IsUserShare = $true } ) $datasets['SmbShares'] = if ($Overrides.ContainsKey('SmbShares')) { $Overrides['SmbShares'] } else { $shareRows } $datasets['FileShareSummary'] = @([pscustomobject]@{ DeepScanPerformed = $true }) $datasets['NtfsAclSummary'] = @( [pscustomobject]@{ Share = 'Finance'; Path = 'D:\Shares\Finance'; TopLevelFolders = 24; FileCount = 41000; TotalSizeGB = 210; LargeFilesOverThreshold = 2; OldFilesOverYears = 900; RecentlyModified30d = 300; MaxDepthScanned = 3; RecycleBinIncluded = $false; RecycleBinFilesFound = 12; RecycleBinSizeGB = 2 } [pscustomobject]@{ Share = 'Engineering'; Path = 'D:\Shares\Engineering'; TopLevelFolders = 58; FileCount = 120000; TotalSizeGB = 890; LargeFilesOverThreshold = 15; OldFilesOverYears = 2200; RecentlyModified30d = 4100; MaxDepthScanned = 3; RecycleBinIncluded = $false; RecycleBinFilesFound = 30; RecycleBinSizeGB = 6 } ) $datasets['Printers'] = @( [pscustomobject]@{ Name = 'Finance-Printer'; DriverName = 'HP Universal'; PortName = 'IP_10.0.0.51'; Shared = $true } [pscustomobject]@{ Name = 'HR-Printer'; DriverName = 'HP Universal'; PortName = 'IP_10.0.0.52'; Shared = $true } [pscustomobject]@{ Name = 'Warehouse-Label'; DriverName = 'Zebra ZPL'; PortName = 'USB001'; Shared = $true } ) $datasets['SecurityPosture'] = @([pscustomobject]@{ Smb1Enabled = $false; RdpEnabledNoNla = $false; AnyAvOrEdrDetected = $true; LocalAdminCount = 3 }) $datasets['Volumes'] = @([pscustomobject]@{ DriveLetter = 'D'; PercentFree = 22; FreeGB = 440; SizeGB = 2000 }) $edges.Add((New-DemoEdge 'SmbShare' 'Finance' 'ServesPath' 'D:\Shares\Finance' 'FileShares')) $edges.Add((New-DemoEdge 'SmbShare' 'Engineering' 'ServesPath' 'D:\Shares\Engineering' 'FileShares')) $edges.Add((New-DemoEdge 'Printer' 'Finance-Printer' 'UsesPort' 'IP_10.0.0.51' 'Printers')) } 'AgingNearEol' { $datasets['ExecutionContext'] = @([pscustomobject]@{ IsAdmin = $true }) $datasets['OperatingSystem'] = @([pscustomobject]@{ Caption = 'Windows Server 2012 R2 Standard'; BuildNumber = '9600'; IsEndOfLifeOrNear = $true }) $datasets['PendingReboot'] = @([pscustomobject]@{ RebootPending = $true }) $datasets['SecurityPosture'] = @([pscustomobject]@{ Smb1Enabled = $true; RdpEnabledNoNla = $true; AnyAvOrEdrDetected = $false; LocalAdminCount = 11 }) $datasets['Services'] = @( [pscustomobject]@{ Name = 'LegacyAppSvc'; DisplayName = 'Legacy Line-of-Business App'; StartName = 'CORP\svc-legacy'; PathName = 'C:\LegacyApp\svc.exe'; PathExists = $true; UnquotedPathWithSpaces = $true; IsNonMicrosoftAutoStart = $true } ) $datasets['InstalledApplications'] = @( [pscustomobject]@{ DisplayName = 'Legacy ERP 3.2'; Publisher = 'Contoso Software'; InstallLocation = 'C:\LegacyApp'; SystemComponent = $false } ) $datasets['Volumes'] = @([pscustomobject]@{ DriveLetter = 'C'; PercentFree = 3; FreeGB = 6; SizeGB = 200 }) $datasets['Certificates'] = @([pscustomobject]@{ Subject = 'CN=legacy01.corp.local'; NotAfter = '2026-10-01'; Thumbprint = 'DEAD00BEEF'; ExpiringSoon = $true; HasPrivateKey = $true }) $datasets['ListeningPorts'] = @([pscustomobject]@{ LocalPort = 8080; Process = 'legacyapp'; ServiceName = 'LegacyAppSvc' }) $edges.Add((New-DemoEdge 'Service' 'LegacyAppSvc' 'RunsAs' 'CORP\svc-legacy' 'Services')) } 'HyperVHost' { $datasets['ExecutionContext'] = @([pscustomobject]@{ IsAdmin = $true }) $datasets['OperatingSystem'] = @([pscustomobject]@{ Caption = 'Windows Server 2022 Datacenter'; BuildNumber = '20348'; IsEndOfLifeOrNear = $false }) $datasets['PendingReboot'] = @([pscustomobject]@{ RebootPending = $false }) $datasets['RolesFeatures'] = @([pscustomobject]@{ Name = 'Hyper-V'; DisplayName = 'Hyper-V'; InstallState = 'Installed' }) $vmRows = @( [pscustomobject]@{ Name = 'VM-App01'; State = 'Running'; Generation = 2; HasCheckpoints = $true } [pscustomobject]@{ Name = 'VM-App02'; State = 'Running'; Generation = 2; HasCheckpoints = $false } [pscustomobject]@{ Name = 'VM-Test01'; State = 'Off'; Generation = 1; HasCheckpoints = $true } [pscustomobject]@{ Name = 'VM-DB01'; State = 'Running'; Generation = 2; HasCheckpoints = $false } ) $datasets['HyperVVMs'] = if ($Overrides.ContainsKey('HyperVVMs')) { $Overrides['HyperVVMs'] } else { $vmRows } $datasets['PerformanceSnapshot'] = @([pscustomobject]@{ CpuPercent = 78 }) $datasets['SecurityPosture'] = @([pscustomobject]@{ Smb1Enabled = $false; RdpEnabledNoNla = $false; AnyAvOrEdrDetected = $true; LocalAdminCount = 3 }) $datasets['Volumes'] = @([pscustomobject]@{ DriveLetter = 'D'; PercentFree = 14; FreeGB = 420; SizeGB = 3000 }) foreach ($vm in $vmRows) { $edges.Add((New-DemoEdge 'Server' $computerName 'HostsVM' $vm.Name 'HyperVVMs')) } } } # Apply any remaining overrides not already handled inline above (generic dataset replacement). foreach ($key in $Overrides.Keys) { if (-not $datasets.Contains($key)) { continue } $datasets[$key] = $Overrides[$key] } return [pscustomobject]@{ ComputerName = $computerName Datasets = $datasets DependencyEdges = @($edges) } } function New-DemoEdge { param([string]$SourceType, [string]$SourceName, [string]$DependencyType, [string]$Target, [string]$SourceDataset) return [pscustomobject]@{ SourceType = $SourceType; SourceName = $SourceName; DependencyType = $DependencyType; Target = $Target; SourceDataset = $SourceDataset } } #endregion #region Pipeline ------------------------------------------------------------------ function New-DemoServerRun { <# Drives the real pipeline (synthesis modules, report writers, archive) against one persona's synthetic data - the exact sequence tests\Invoke-OutputSmokeTest.ps1 already proves works. Kept in sync with that file's own "Real synthesis + output chain" section by hand; a divergence between the two is a maintenance trap worth checking for if either one changes. #> param( [Parameter(Mandatory)][string]$PersonaName, [Parameter(Mandatory)][string]$EngagementFolder, [string]$ProjectType = 'GeneralDiscovery', [datetime]$AsOfTime = (Get-Date), [hashtable]$Overrides = @{} ) $persona = Get-DemoServerPersona -PersonaName $PersonaName -Overrides $Overrides $cfg = Get-DiscoveryConfigBundle -ConfigDirectory (Join-Path $root 'config') $runFolderName = 'Discover-WindowsServer_{0}_{1}' -f $persona.ComputerName, $AsOfTime.ToString('yyyyMMdd_HHmmss') $out = Join-Path $EngagementFolder $runFolderName $ctx = New-DiscoveryContext -Mode Deep -ProjectType $ProjectType -ComplianceLens 'None' -OutputPath $out -IsAdmin $true -Config $cfg ` -Parameters @{ Mode = 'Deep'; GenerateEvidenceManifest = $true; SkipZip = $false } $ctx.ComputerName = $persona.ComputerName Ensure-Directory -Path $out | Out-Null # Mirror of $folderMap in Discover-WindowsServer.ps1 / Invoke-OutputSmokeTest.ps1 - keep in sync. $folderMap = [ordered]@{ Reports = 'reports'; ReportsSupporting = 'reports\supporting'; ClientSafe = 'reports\supporting\client-safe'; Internal = 'reports\supporting\internal'; EvidenceRoot = 'evidence'; Csv = 'evidence\data\csv'; Json = 'evidence\data\json'; Logs = 'evidence\logs'; Raw = 'evidence\raw'; Status = 'evidence\status'; Evidence = 'evidence\manifest'; Archive = 'archive' } $paths = [ordered]@{} foreach ($k in $folderMap.Keys) { $p = Join-Path $out $folderMap[$k]; Ensure-Directory -Path $p | Out-Null; $paths[$k] = $p } $ctx.Paths = $paths $ctx.LogPaths = [ordered]@{ Summary = (Join-Path $paths['Logs'] 'summary.txt'); Errors = (Join-Path $paths['Logs'] 'errors.txt'); Warnings = (Join-Path $paths['Logs'] 'warnings.txt'); Debug = (Join-Path $paths['Logs'] 'debug.log') } foreach ($lp in $ctx.LogPaths.Values) { New-Item -ItemType File -Path $lp -Force | Out-Null } $ctx.IncludedModules = @($persona.Datasets.Keys | Select-Object -Unique) foreach ($name in $persona.Datasets.Keys) { Add-DataSet -Context $ctx -Name $name -Rows $persona.Datasets[$name] -Visibility 'Internal' -SourceModule 'Demo' | Out-Null } foreach ($e in $persona.DependencyEdges) { Add-DependencyEdge -Context $ctx -SourceType $e.SourceType -SourceName $e.SourceName -DependencyType $e.DependencyType -Target $e.Target -SourceDataset $e.SourceDataset | Out-Null } # Real synthesis + output chain - see this function's own doc-comment. Invoke-SynthesisModule -Context $ctx -Name 'Applications' -ModulesRoot (Join-Path $root 'modules') -EntryCommand 'Invoke-DiscoveryFingerprintSynthesis' foreach ($m in @('RiskEngine', 'DecommissionReadiness', 'ScopeLanguage', 'ClientInterviewPack')) { Invoke-SynthesisModule -Context $ctx -Name $m -ModulesRoot (Join-Path $root 'modules') } Build-ApplicationValidationMatrix -Context $ctx Build-ContextListDatasets -Context $ctx Write-DiscoveryPlan -Context $ctx -Path (Join-Path $out 'evidence\discovery-plan.md') Write-DiscoveryOutputs -Context $ctx Invoke-SynthesisModule -Context $ctx -Name 'ReportBuilder' -ModulesRoot (Join-Path $root 'modules') -EntryCommand 'Invoke-DiscoveryReportBuild' Invoke-SynthesisModule -Context $ctx -Name 'EvidenceManifest' -ModulesRoot (Join-Path $root 'modules') -EntryCommand 'Invoke-DiscoveryEvidenceManifest' Update-StatusFile -Context $ctx -Phase 'Complete' -CompletedModules 6 -TotalModules 6 Compress-Folder -SourceFolder $out -DestinationZip (Join-Path $ctx.Paths['Archive'] 'run.zip') -ExcludeChildFolders @('archive') | Out-Null return $out } function New-DemoEngagement { <# Builds one (or, with -TwoSnapshots, two) demo engagement folder(s) - see this file's own header comment for the full scenario. #> param( [Parameter(Mandatory)][string]$OutputFolder, [string[]]$Personas = @('DomainController', 'SqlServer', 'FilePrintServer', 'AgingNearEol', 'HyperVHost'), [string]$ProjectType = 'GeneralDiscovery', [switch]$TwoSnapshots ) $baselineTime = (Get-Date).AddDays(-7) $baselineFolder = Join-Path $OutputFolder ('Fleet_{0}' -f $baselineTime.ToString('yyyyMMdd_HHmmss')) Ensure-Directory -Path $baselineFolder | Out-Null foreach ($p in $Personas) { New-DemoServerRun -PersonaName $p -EngagementFolder $baselineFolder -ProjectType $ProjectType -AsOfTime $baselineTime | Out-Null } $result = [ordered]@{ Snapshot1 = $baselineFolder; Snapshot2 = $null } if ($TwoSnapshots) { # Small, reproducible, per-persona differences - not randomized, so the demo is the same # every time it's generated. Only personas with a defined override actually drift; the # rest re-run identically, which is realistic (not every server changes every week). $driftOverrides = @{ 'FilePrintServer' = @{ SmbShares = @( [pscustomobject]@{ Name = 'Finance'; Path = 'D:\Shares\Finance'; IsUserShare = $true } [pscustomobject]@{ Name = 'HR'; Path = 'D:\Shares\HR'; IsUserShare = $true } [pscustomobject]@{ Name = 'Public'; Path = 'D:\Shares\Public'; IsUserShare = $true } [pscustomobject]@{ Name = 'Engineering'; Path = 'D:\Shares\Engineering'; IsUserShare = $true } [pscustomobject]@{ Name = 'Archive2026'; Path = 'D:\Shares\Archive2026'; IsUserShare = $true } ) } 'HyperVHost' = @{ HyperVVMs = @( [pscustomobject]@{ Name = 'VM-App01'; State = 'Running'; Generation = 2; HasCheckpoints = $false } [pscustomobject]@{ Name = 'VM-App02'; State = 'Running'; Generation = 2; HasCheckpoints = $false } [pscustomobject]@{ Name = 'VM-Test01'; State = 'Off'; Generation = 1; HasCheckpoints = $true } [pscustomobject]@{ Name = 'VM-DB01'; State = 'Running'; Generation = 2; HasCheckpoints = $false } [pscustomobject]@{ Name = 'VM-App03'; State = 'Running'; Generation = 2; HasCheckpoints = $false } ) } } $currentTime = Get-Date $currentFolder = Join-Path $OutputFolder ('Fleet_{0}' -f $currentTime.ToString('yyyyMMdd_HHmmss')) Ensure-Directory -Path $currentFolder | Out-Null foreach ($p in $Personas) { $overrides = if ($driftOverrides.ContainsKey($p)) { $driftOverrides[$p] } else { @{} } New-DemoServerRun -PersonaName $p -EngagementFolder $currentFolder -ProjectType $ProjectType -AsOfTime $currentTime -Overrides $overrides | Out-Null } $result.Snapshot2 = $currentFolder } return [pscustomobject]$result } #endregion # Only runs when executed directly - dot-source for the functions above (Pester, or future # GUI integration), same convention as every other file in tools\. if ($MyInvocation.InvocationName -ne '.') { Import-Module (Join-Path $root 'modules/Core/Core.psm1') -Force -DisableNameChecking -Global Import-Module (Join-Path $root 'modules/Output/Output.psm1') -Force -DisableNameChecking -Global Import-Module (Join-Path $root 'Discover-WindowsServer.psm1') -Force -DisableNameChecking -Global Ensure-Directory -Path $OutputFolder | Out-Null $result = New-DemoEngagement -OutputFolder $OutputFolder -Personas $Personas -ProjectType $ProjectType -TwoSnapshots:$TwoSnapshots Write-Host ("Demo engagement generated: {0}" -f $result.Snapshot1) -ForegroundColor Green if ($result.Snapshot2) { Write-Host ("Second (drifted) snapshot: {0}" -f $result.Snapshot2) -ForegroundColor Green } Write-Host '' Write-Host 'Next steps:' $latestFolder = if ($result.Snapshot2) { $result.Snapshot2 } else { $result.Snapshot1 } Write-Host (" .\tools\Merge-FleetDiscoveryResults.ps1 -EngagementFolder '{0}'" -f $latestFolder) if ($result.Snapshot2) { Write-Host (" .\tools\Compare-DiscoveryRuns.ps1 -BaselineEngagementFolder '{0}' -CurrentEngagementFolder '{1}'" -f $result.Snapshot1, $result.Snapshot2) } } |