diff --git a/errorlog.md b/errorlog.md index dd9eb04b..4352620f 100644 --- a/errorlog.md +++ b/errorlog.md @@ -19,6 +19,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ยท +2026-07-07 | Howard-Home | ps-encoded | encode produced empty output [ctx: src=scratch-recon.ps1] (x2) + 2026-07-07 | Howard-Home | windows/scheduled-task | [friction] EDR Isolation Watcher task launched bash.exe directly (Hidden=False, every 10min) -> console window flashed on desktop; recurring class also hit GPS-RMM-Progress today. FIX: wscript VBS wrapper (style 0) for bash, pythonw+-Hidden for python; fixed both installers [ctx: ref=feedback_scheduled_task_no_console_flash host=Howard-Home task='ClaudeTools - EDR Isolation Watcher'] 2026-07-07 | Howard-Home | datto-edr | Datto EDR request failed: [ctx: cmd=detections] diff --git a/scratch-bkuprecon.ps1 b/scratch-bkuprecon.ps1 new file mode 100644 index 00000000..60c32866 --- /dev/null +++ b/scratch-bkuprecon.ps1 @@ -0,0 +1,18 @@ +$ErrorActionPreference='SilentlyContinue' +"==== WINDOWS SERVER BACKUP FEATURE ====" +(Get-WindowsFeature Windows-Server-Backup | Select-Object Name,Installed | Out-String) +"==== ALL DISKS (spot a USB/backup disk) ====" +Get-Disk | Select-Object Number,FriendlyName,BusType,@{n='SizeGB';e={[math]::Round($_.Size/1GB,0)}},OperationalStatus,PartitionStyle | Format-Table -Auto | Out-String +"==== VOLUMES ====" +Get-Volume | Where-Object {$_.DriveType -ne 'CD-ROM'} | Select-Object DriveLetter,FileSystemLabel,DriveType,@{n='SizeGB';e={[math]::Round($_.Size/1GB,1)}},@{n='FreeGB';e={[math]::Round($_.SizeRemaining/1GB,1)}} | Format-Table -Auto | Out-String +"==== C: USED (what we must protect) ====" +$c=Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" +"Used GB: $([math]::Round(($c.Size-$c.FreeSpace)/1GB,1)) Free GB: $([math]::Round($c.FreeSpace/1GB,1))" +"==== VSS WRITER HEALTH (must be stable/no-error before backup) ====" +$w = vssadmin list writers 2>$null +($w | Select-String -Pattern 'Writer name:|State:|Last error:' | Out-String) +"==== EXISTING WBADMIN BACKUP HISTORY ====" +wbadmin get versions 2>&1 | Out-String +"==== REACH NEW SERVER? ====" +Test-NetConnection -ComputerName TPS-SVR -Port 445 -InformationLevel Quiet 2>$null +"==== DONE ====" diff --git a/scratch-dry-ca.ps1 b/scratch-dry-ca.ps1 new file mode 100644 index 00000000..524575bf --- /dev/null +++ b/scratch-dry-ca.ps1 @@ -0,0 +1,20 @@ +$ErrorActionPreference='SilentlyContinue' +"== CA service + identity ==" +Get-Service CertSvc | Select-Object Name,Status,StartType | Format-Table -Auto | Out-String +certutil -cainfo 2>&1 | Select-String 'CA name|CA type|Subject|Expires|Provider|hash' | Out-String +"== CA database + log files (backup sizing) ==" +Get-ChildItem 'C:\Windows\System32\CertLog' -File 2>$null | Select-Object Name,@{n='MB';e={[math]::Round($_.Length/1MB,2)}} | Format-Table -Auto | Out-String +"== Private-key provider / exportability (determines backupKey method) ==" +certutil -store My 2>&1 | Select-String 'TPS-TPS-SERVER-CA|Provider =|Private key is NOT exportable|Signature test|Key Container' | Out-String +"== Config to reproduce on the rebuilt CA ==" +"-- Validity period --" +certutil -getreg CA\ValidityPeriodUnits 2>&1 | Select-String 'Values|=' | Out-String +certutil -getreg CA\ValidityPeriod 2>&1 | Select-String 'Values|=' | Out-String +"-- CRL publication URLs --" +certutil -getreg CA\CRLPublicationURLs 2>&1 | Out-String +"-- AIA publication URLs --" +certutil -getreg CA\CACertPublicationURLs 2>&1 | Out-String +"-- CRL period --" +certutil -getreg CA\CRLPeriodUnits 2>&1 | Select-String 'Values|=' | Out-String +certutil -getreg CA\CRLPeriod 2>&1 | Select-String 'Values|=' | Out-String +"== DONE ==" diff --git a/scratch-dry-promo.ps1 b/scratch-dry-promo.ps1 new file mode 100644 index 00000000..91b96f10 --- /dev/null +++ b/scratch-dry-promo.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference='SilentlyContinue' +Import-Module ActiveDirectory -ErrorAction SilentlyContinue +"== OS / edition (must be Std/DC-capable) ==" +$os=Get-CimInstance Win32_OperatingSystem; "$($os.Caption) $($os.Version)" +"== AD DS deployment module (needed to promote + to run Test-ADDSDomainControllerInstallation) ==" +Get-Module -ListAvailable ADDSDeployment | Select-Object Name,Version | Format-Table -Auto | Out-String +"== Secure channel to domain ==" +nltest /sc_query:TPS.local 2>&1 | Out-String +"== Locate a DC for the domain ==" +nltest /dsgetdc:TPS.local 2>&1 | Out-String +"== Port reachability to current DC 192.168.1.125 (need all OPEN) ==" +foreach($p in 53,88,135,389,445,3268,636){ $r=Test-NetConnection -ComputerName 192.168.1.125 -Port $p -WarningAction SilentlyContinue; "{0,-6} {1}" -f $p, $(if($r.TcpTestSucceeded){'OPEN'}else{'CLOSED'}) } +"== Time offset vs DC (Kerberos needs < 5 min) ==" +w32tm /stripchart /computer:192.168.1.125 /samples:1 /dataonly 2>&1 | Select-Object -Last 3 | Out-String +"== Schema objectVersion (87=2016, 88=2019 -> promotion auto-adpreps if 87) ==" +$sch=(Get-ADRootDSE).schemaNamingContext +"objectVersion: $((Get-ADObject $sch -Property objectVersion).objectVersion)" +"== Free space C: (NTDS + SYSVOL target) ==" +$v=Get-Volume -DriveLetter C; "Free GB: $([math]::Round($v.SizeRemaining/1GB,1))" +"== This box's current DNS (post-promo it should point to itself) ==" +Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object {$_.ServerAddresses} | Select-Object InterfaceAlias,@{n='DNS';e={$_.ServerAddresses -join ','}} | Format-Table -Auto | Out-String +"== DONE ==" diff --git a/scratch-dry-robo.ps1 b/scratch-dry-robo.ps1 new file mode 100644 index 00000000..04e459a5 --- /dev/null +++ b/scratch-dry-robo.ps1 @@ -0,0 +1,13 @@ +$ErrorActionPreference='SilentlyContinue' +function DryCopy($src){ + if(-not (Test-Path $src)){ "### $src`nMISSING`n"; return } + $out = robocopy $src 'C:\__dryrun_nul__' /L /E /NFL /NDL /NP /R:0 /W:0 /BYTES /XJ 2>&1 + "### $src" + ($out | Select-String 'Dirs :|Files :|Bytes :|ERROR|Access is denied') | Out-String +} +DryCopy 'C:\Share\Quickbooks' +DryCopy 'C:\ServerFolders\Users' +DryCopy 'C:\ServerFolders\Company' +DryCopy 'C:\ServerFolders\Folder Redirection' +DryCopy 'C:\Share\Deployment' +"== DONE ==" diff --git a/scratch-mkshare.ps1 b/scratch-mkshare.ps1 new file mode 100644 index 00000000..81ea40d9 --- /dev/null +++ b/scratch-mkshare.ps1 @@ -0,0 +1,28 @@ +$ErrorActionPreference='Stop' +$path='C:\Backups\TPS-SERVER' +New-Item -ItemType Directory -Path $path -Force | Out-Null + +# SMB share granting the old server's machine account + Domain Admins +if(Get-SmbShare -Name 'TPSBackup' -ErrorAction SilentlyContinue){ + Write-Host "share TPSBackup already exists" +} else { + New-SmbShare -Name 'TPSBackup' -Path $path -FullAccess 'TPS\Domain Admins','TPS\TPS-SERVER$' -CachingMode None | Out-Null + Write-Host "created share TPSBackup" +} + +# NTFS ACL: SYSTEM, Administrators, Domain Admins, TPS-SERVER$ full control +$acl = Get-Acl $path +foreach($id in @('TPS\Domain Admins','TPS\TPS-SERVER$','BUILTIN\Administrators','NT AUTHORITY\SYSTEM')){ + try { + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($id,'FullControl','ContainerInherit,ObjectInherit','None','Allow') + $acl.AddAccessRule($rule) + } catch { Write-Host "acl skip $id : $_" } +} +Set-Acl -Path $path -AclObject $acl + +"==== RESULT ====" +Get-SmbShare -Name TPSBackup | Format-List Name,Path,FullAccess | Out-String +"UNC: \\TPS-SVR\TPSBackup" +$v=Get-Volume -DriveLetter C +"Target free space GB: $([math]::Round($v.SizeRemaining/1GB,1))" +"==== DONE ====" diff --git a/scratch-mkstage.ps1 b/scratch-mkstage.ps1 new file mode 100644 index 00000000..e962fdc9 --- /dev/null +++ b/scratch-mkstage.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference='Stop' +"== post-reboot state ==" +$pfro=(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations +"PendingFileRenameOperations now: $([bool]$pfro)" +$os=Get-CimInstance Win32_OperatingSystem +"Last boot: $($os.LastBootUpTime) Uptime min: $([math]::Round(((Get-Date)-$os.LastBootUpTime).TotalMinutes,0))" +"Activation: $((Get-CimInstance SoftwareLicensingProduct -Filter "ApplicationID='55c92734-d682-4d71-983e-d6ec3f16059f' AND LicenseStatus=1" | Select-Object -First 1 -Expand LicenseStatus) ) (1 = Licensed)" + +"== create staging root + share (push target for pre-seed) ==" +New-Item -ItemType Directory -Path 'C:\Share' -Force | Out-Null +if(Get-SmbShare -Name 'Share' -ErrorAction SilentlyContinue){ "share 'Share' already exists" } else { + New-SmbShare -Name 'Share' -Path 'C:\Share' -FullAccess 'TPS\Domain Admins','TPS\TPS-SERVER$' -CachingMode None | Out-Null + "created share 'Share'" +} +$acl=Get-Acl 'C:\Share' +foreach($id in @('TPS\Domain Admins','TPS\TPS-SERVER$','BUILTIN\Administrators','NT AUTHORITY\SYSTEM')){ + $r=New-Object System.Security.AccessControl.FileSystemAccessRule($id,'FullControl','ContainerInherit,ObjectInherit','None','Allow'); $acl.AddAccessRule($r) +} +Set-Acl 'C:\Share' $acl +Get-SmbShareAccess -Name Share | Select-Object AccountName,AccessRight,AccessControlType | Format-Table -Auto | Out-String +"UNC ready: \\TPS-SVR\Share -> C:\Share" +"== DONE ==" diff --git a/scratch-preflight.ps1 b/scratch-preflight.ps1 new file mode 100644 index 00000000..4970e57e --- /dev/null +++ b/scratch-preflight.ps1 @@ -0,0 +1,50 @@ +$ErrorActionPreference='SilentlyContinue' +Import-Module ActiveDirectory -ErrorAction SilentlyContinue + +"==== FUNCTIONAL LEVELS + DOMAIN ====" +$f=Get-ADForest; $d=Get-ADDomain +"Forest mode: $($f.ForestMode) Domain mode: $($d.DomainMode)" +"Domain: $($d.DNSRoot) NetBIOS: $($d.NetBIOSName) PDC: $($d.PDCEmulator)" + +"`n==== CURRENT DOMAIN CONTROLLERS ====" +Get-ADDomainController -Filter * | Select-Object Name,IPv4Address,IsGlobalCatalog,OperatingSystem,Site | Format-Table -Auto | Out-String + +"==== FSMO HOLDERS ====" +netdom query fsmo 2>&1 | Out-String + +"==== LINGERING TPS-SVR DC METADATA (must be clean before promo) ====" +$cfg=(Get-ADRootDSE).configurationNamingContext +"-- computer object(s) matching TPS-SVR --" +Get-ADComputer -Filter 'Name -like "*SVR*"' -Properties operatingSystem,whenCreated | Select-Object Name,DistinguishedName,operatingSystem,whenCreated | Format-Table -Auto | Out-String +"-- 'server' objects in config Sites (each real/former DC has one) --" +Get-ADObject -SearchBase "CN=Sites,$cfg" -Filter "objectClass -eq 'server'" -Properties dNSHostName | Select-Object Name,dNSHostName | Format-Table -Auto | Out-String +"-- nTDSDSA (NTDS Settings) objects = active DC replicas --" +Get-ADObject -SearchBase "CN=Sites,$cfg" -Filter "objectClass -eq 'nTDSDSA'" | Select-Object DistinguishedName | Format-Table -Auto | Out-String +"-- Domain Controllers OU members --" +Get-ADComputer -SearchBase "OU=Domain Controllers,$($d.DistinguishedName)" -Filter * | Select-Object Name | Format-Table -Auto | Out-String + +"==== AD REPLICATION HEALTH ====" +repadmin /replsummary 2>&1 | Out-String + +"==== DHCP AUTHORIZED IN AD (empty => MS DHCP not authoritative) ====" +Get-DhcpServerInDC 2>&1 | Out-String + +"==== CERTIFICATE AUTHORITY (assess if in use) ====" +certutil -cainfo 2>&1 | Select-String 'CA type|CA name|Subject|Expires|Provider' | Out-String +"-- issued (non-revoked, non-expired-status) cert rows, first 30 --" +certutil -view -restrict "Disposition=20" -out "RequestID,CommonName,CertificateTemplate,NotAfter" csv 2>&1 | Select-Object -First 30 | Out-String +"-- total issued rows --" +$rows = certutil -view -restrict "Disposition=20" -out "RequestID" csv 2>&1 +"issued cert rows (incl header): $(($rows | Measure-Object -Line).Lines)" + +"==== OLD SERVER IP CONFIG (plan Option B swap) ====" +Get-NetIPConfiguration | ForEach-Object { + "IF: $($_.InterfaceAlias) IP: $($_.IPv4Address.IPAddress) GW: $($_.IPv4DefaultGateway.NextHop) DNS: $($_.DNSServer.ServerAddresses -join ',')" +} +"-- all IPv4 addrs --" +Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -notlike '169.*' -and $_.IPAddress -ne '127.0.0.1'} | Select-Object IPAddress,PrefixLength,InterfaceAlias | Format-Table -Auto | Out-String + +"==== DFS NAMESPACES (Folder Redirection / Shared Folders use these) ====" +Get-DfsnRoot 2>&1 | Out-String + +"==== DONE ====" diff --git a/scratch-preflight2.ps1 b/scratch-preflight2.ps1 new file mode 100644 index 00000000..336cce83 --- /dev/null +++ b/scratch-preflight2.ps1 @@ -0,0 +1,29 @@ +$ErrorActionPreference='SilentlyContinue' + +"==== A. WHAT USES EACH SERVER IP ====" +"-- DNS A records pointing at the server's IPs --" +$targets = '192.168.1.120','192.168.1.125','10.10.10.5' +Get-DnsServerResourceRecord -ZoneName 'TPS.local' -RRType A 2>$null | + Where-Object { $_.RecordData.IPv4Address.IPAddressToString -in $targets } | + Select-Object HostName,@{n='IP';e={$_.RecordData.IPv4Address.IPAddressToString}} | + Sort-Object IP | Format-Table -Auto | Out-String + +"-- TCP listeners bound to a SPECIFIC IP (reveals services pinned to .120/.125/10.x) --" +Get-NetTCPConnection -State Listen 2>$null | + Where-Object { $_.LocalAddress -notin '0.0.0.0','::','127.0.0.1','::1' } | + Select-Object LocalAddress,LocalPort,@{n='Process';e={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} | + Sort-Object LocalAddress,LocalPort | Format-Table -Auto | Out-String + +"-- IIS websites + bindings (Essentials Remote Web Access, etc.) --" +Import-Module WebAdministration -ErrorAction SilentlyContinue +Get-Website 2>$null | Select-Object Name,State,@{n='Bindings';e={ ($_.bindings.Collection | ForEach-Object { $_.protocol + ':' + $_.bindingInformation }) -join ' | ' }} | Format-Table -Auto -Wrap | Out-String + +"==== B. IS THE CA STILL ACTIVELY ISSUING? ====" +"-- Templates the CA is currently published to issue --" +certutil -CATemplates 2>&1 | Out-String +"-- MOST RECENT issued certs (highest RequestIDs = newest) --" +certutil -view -restrict "Disposition=20" -out "RequestID,CommonName,CertificateTemplate,NotAfter" csv 2>&1 | Select-Object -Last 15 | Out-String +"-- Any GPO that might drive cert autoenrollment --" +Get-GPO -All 2>$null | Select-Object DisplayName,ModificationTime | Sort-Object DisplayName | Format-Table -Auto | Out-String + +"==== DONE ====" diff --git a/scratch-prepcheck.ps1 b/scratch-prepcheck.ps1 new file mode 100644 index 00000000..44c50233 --- /dev/null +++ b/scratch-prepcheck.ps1 @@ -0,0 +1,27 @@ +$ErrorActionPreference='SilentlyContinue' +"== PENDING REBOOT CHECKS (all should be False) ==" +"CBS RebootPending: $([bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'))" +"WindowsUpdate RebootRequired: $([bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'))" +$pfro = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations +"PendingFileRenameOperations: $([bool]$pfro)" +$cn1=(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName').ComputerName +$cn2=(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName').ComputerName +"Rename pending: $($cn1 -ne $cn2) (active=$cn1 / configured=$cn2)" + +"== ROLE / SERVICE STATE (is AD DS ready to promote?) ==" +Get-WindowsFeature AD-Domain-Services,RSAT-ADDS,DNS,DHCP | Select-Object Name,InstallState | Format-Table -Auto | Out-String +Get-Service NTDS,DNS,DHCPServer,Netlogon,W32Time | Select-Object Name,Status,StartType | Format-Table -Auto | Out-String + +"== EXISTING TARGET PATHS (for data pre-seed) ==" +foreach($p in 'C:\Share','C:\Share\Quickbooks','C:\Share\Deployment'){ "$p exists: $(Test-Path $p)" } + +"== DISK C: ==" +Get-Volume -DriveLetter C | Select-Object @{n='FreeGB';e={[math]::Round($_.SizeRemaining/1GB,1)}},@{n='SizeGB';e={[math]::Round($_.Size/1GB,1)}} | Format-Table -Auto | Out-String + +"== UPTIME / LAST BOOT ==" +$os=Get-CimInstance Win32_OperatingSystem +"Last boot: $($os.LastBootUpTime) Uptime days: $([math]::Round(((Get-Date)-$os.LastBootUpTime).TotalDays,1))" + +"== PENDING WINDOWS UPDATES (a big backlog = reboot churn) ==" +try { $s=(New-Object -ComObject Microsoft.Update.Session).CreateUpdateSearcher(); $r=$s.Search("IsInstalled=0 and IsHidden=0"); "Pending updates: $($r.Updates.Count)" } catch { "update query n/a: $_" } +"== DONE ==" diff --git a/scratch-preseed.ps1 b/scratch-preseed.ps1 new file mode 100644 index 00000000..ede4a6bd --- /dev/null +++ b/scratch-preseed.ps1 @@ -0,0 +1,15 @@ +$ErrorActionPreference='Stop' +$log='C:\Windows\Temp\tps-preseed.log' +# Pre-seed: data+attributes+timestamps only (ACLs re-applied fresh at cutover, avoids remote-privilege issues). +# /B backup mode reads open/locked files where possible; locked live QB .QBW skips fast (/R:1) and is recopied clean at cutover. +$rc = 'robocopy "C:\Share" "\\TPS-SVR\Share" /E /COPY:DAT /DCOPY:DAT /B /R:1 /W:2 /Z /XJ /MT:16 /NFL /NDL /NP /LOG:' + $log +$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument ('/c ' + $rc) +$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest +$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 12) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries +$task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings +Register-ScheduledTask -TaskName 'TPS-Preseed-Copy' -InputObject $task -Force | Out-Null +Start-ScheduledTask -TaskName 'TPS-Preseed-Copy' +Start-Sleep -Seconds 12 +"Task state: $((Get-ScheduledTask -TaskName 'TPS-Preseed-Copy').State)" +"-- log head --" +if(Test-Path $log){ Get-Content $log -TotalCount 14 } else { "(log not created yet)" } diff --git a/scratch-startbkup.ps1 b/scratch-startbkup.ps1 new file mode 100644 index 00000000..f263c165 --- /dev/null +++ b/scratch-startbkup.ps1 @@ -0,0 +1,26 @@ +$ErrorActionPreference='Stop' +$target='\\TPS-SVR\TPSBackup' + +# Pre-flight: confirm we can write to the target as the machine account +$probe = Join-Path $target ("_probe_{0}.txt" -f $env:COMPUTERNAME) +try { Set-Content -Path $probe -Value 'ok' -ErrorAction Stop; Remove-Item $probe -Force; Write-Host "target writable: YES" } +catch { Write-Host "target writable: NO -> $_"; throw } + +# Detached one-time scheduled task so the backup outlives this RMM command +$log = 'C:\Windows\Temp\tps-bmr.log' +$cmd = "wbadmin start backup -backupTarget:$target -allCritical -vssCopy -quiet" +$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument "/c $cmd > `"$log`" 2>&1" +$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 12) +$task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings +Register-ScheduledTask -TaskName 'TPS-BMR-Backup' -InputObject $task -Force | Out-Null +Start-ScheduledTask -TaskName 'TPS-BMR-Backup' +Start-Sleep -Seconds 12 + +"==== TASK STATE ====" +Get-ScheduledTask -TaskName 'TPS-BMR-Backup' | Select-Object TaskName,State | Format-Table -Auto | Out-String +"==== WBADMIN STATUS ====" +wbadmin get status 2>&1 | Out-String +"==== LOG HEAD ====" +if(Test-Path $log){ Get-Content $log -TotalCount 15 | Out-String } else { "(log not created yet)" } +"==== DONE ===="