<# .SYNOPSIS Creates write-only audit drop share with service account and scheduled archive. .DESCRIPTION Provisions on CS-SERVER: - AD service account svc-audit-upload (no interactive logon) - AD group AuditUploaders (members get write-only access) - D:\Shares\AuditDrop folder with NTFS write-only ACL - Hidden SMB share \\CS-SERVER\AuditDrop$ (Change for AuditUploaders, Full for Domain Admins) - Daily 3am scheduled task that moves yesterday's drops into D:\Shares\AuditDrop_Archive\YYYY-MM-DD\ Idempotent — safe to re-run. .NOTES Run as Domain Admin on CS-SERVER (or any system with AD tools + SMB management installed). Verify the OUs in $svcOuPath and $groupOuPath exist before running, or change them. #> # === VARIABLES === $domainDN = "DC=cascades,DC=local" $svcOuPath = "OU=ServiceAccounts,$domainDN" $groupOuPath = "OU=Groups,$domainDN" $svcUser = "svc-audit-upload" $group = "AuditUploaders" $drop = "D:\Shares\AuditDrop" $archiveRoot = "D:\Shares\AuditDrop_Archive" $scriptPath = "D:\Shares\Scripts\Archive-AuditDrop.ps1" # === 0. Pre-flight: confirm target OUs exist === foreach ($ou in @($svcOuPath, $groupOuPath)) { try { Get-ADOrganizationalUnit -Identity $ou -ErrorAction Stop | Out-Null Write-Host "[OK] OU exists: $ou" } catch { Write-Host "[ERROR] OU not found: $ou" -ForegroundColor Red Write-Host " Create it first, or edit `$svcOuPath / `$groupOuPath in this script." -ForegroundColor Yellow Write-Host " Example: New-ADOrganizationalUnit -Name 'ServiceAccounts' -Path '$domainDN' -ProtectedFromAccidentalDeletion `$true" -ForegroundColor Yellow exit 1 } } # === 1. Create the dedicated upload service account === $svcPwd = Read-Host -AsSecureString -Prompt "Enter password for $svcUser (store in 1Password / SOPS vault)" if (-not (Get-ADUser -Filter "SamAccountName -eq '$svcUser'" -ErrorAction SilentlyContinue)) { New-ADUser ` -Name $svcUser ` -SamAccountName $svcUser ` -DisplayName "Service: Audit JSON Upload" ` -Description "Write-only access to \\CS-SERVER\AuditDrop$. Used by Syncro audit upload script. Do not use interactively." ` -Path $svcOuPath ` -AccountPassword $svcPwd ` -PasswordNeverExpires $true ` -CannotChangePassword $true ` -Enabled $true Write-Host "[OK] Created service account: $svcUser" } else { Write-Host "[OK] Service account already exists: $svcUser" } # === 2. Create the AD group === if (-not (Get-ADGroup -Filter "SamAccountName -eq '$group'" -ErrorAction SilentlyContinue)) { New-ADGroup ` -Name $group ` -SamAccountName $group ` -GroupScope Global ` -GroupCategory Security ` -Path $groupOuPath ` -Description "Members can WRITE-ONLY to \\CS-SERVER\AuditDrop$. No read/list." Write-Host "[OK] Created group: $group" } else { Write-Host "[OK] Group already exists: $group" } # Add service account to group (idempotent) Add-ADGroupMember -Identity $group -Members $svcUser -ErrorAction SilentlyContinue Write-Host "[OK] Membership confirmed: $svcUser in $group" # === 3. Create the drop folder === New-Item -Path $drop -ItemType Directory -Force | Out-Null Write-Host "[OK] Drop folder ready: $drop" # === 4. NTFS write-only permissions === icacls $drop /inheritance:r | Out-Null icacls $drop /grant:r "CASCADES\Domain Admins:(OI)(CI)F" | Out-Null icacls $drop /grant:r "SYSTEM:(OI)(CI)F" | Out-Null icacls $drop /grant:r "CASCADES\${group}:(WD,AD,WA,WEA,RA,REA,RC,S,X)" | Out-Null Write-Host "[OK] NTFS permissions applied. Listing:" icacls $drop # === 5. Create the hidden SMB share === if (-not (Get-SmbShare -Name "AuditDrop$" -ErrorAction SilentlyContinue)) { New-SmbShare ` -Name "AuditDrop$" ` -Path $drop ` -FullAccess "CASCADES\Domain Admins" ` -ChangeAccess "CASCADES\${group}" ` -Description "Audit JSON drop-box. Hidden. Write-only for AuditUploaders group." Write-Host "[OK] Created SMB share: AuditDrop$" } else { Write-Host "[OK] SMB share already exists: AuditDrop$" } Write-Host "[OK] Share access:" Get-SmbShareAccess -Name "AuditDrop$" # === 6. Generate the archive script === # IMPORTANT: backtick-escape any $ that must remain literal in the GENERATED file. # - $drop and $archiveRoot expand NOW (they're fixed paths set above) # - $src, $archive, $_, $(Get-Date ...) must be backtick-escaped so they expand at TASK RUN TIME $archiveScript = @" `$src = "$drop" `$archive = "$archiveRoot\`$(Get-Date -Format yyyy-MM-dd)" New-Item -Path `$archive -ItemType Directory -Force | Out-Null Get-ChildItem -Path `$src -File | Where-Object { `$_.LastWriteTime -lt (Get-Date).AddHours(-2) } | Move-Item -Destination `$archive -Force "@ New-Item -ItemType Directory -Path (Split-Path $scriptPath) -Force | Out-Null $archiveScript | Out-File $scriptPath -Encoding UTF8 -Force Write-Host "[OK] Archive script written: $scriptPath" Write-Host " Contents:" Get-Content $scriptPath | ForEach-Object { Write-Host " $_" } # === 7. Create scheduled task (daily 3am) === $action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" $trigger = New-ScheduledTaskTrigger -Daily -At 3am if (-not (Get-ScheduledTask -TaskName "Archive-AuditDrop" -ErrorAction SilentlyContinue)) { Register-ScheduledTask ` -TaskName "Archive-AuditDrop" ` -Action $action ` -Trigger $trigger ` -RunLevel Highest ` -User "SYSTEM" Write-Host "[OK] Scheduled task created: Archive-AuditDrop (daily 3am)" } else { Write-Host "[OK] Scheduled task already exists: Archive-AuditDrop" } # === 8. Test instructions === Write-Host "" Write-Host "=== TEST FROM A WORKSTATION ===" -ForegroundColor Cyan Write-Host "Run these manually to verify write-only behavior:" Write-Host "" Write-Host " `$cred = Get-Credential cascades\$svcUser" Write-Host " New-PSDrive -Name T -PSProvider FileSystem -Root '\\CS-SERVER\AuditDrop$' -Credential `$cred" Write-Host " 'hello' | Out-File T:\test_`$(hostname).txt # SHOULD SUCCEED" Write-Host " Get-ChildItem T:\ # SHOULD FAIL (Access Denied)" Write-Host " Get-Content T:\test_`$(hostname).txt # SHOULD FAIL (Access Denied)" Write-Host " Remove-PSDrive T" Write-Host "" Write-Host "If all three SHOULD-FAIL lines actually return Access Denied, the drop-box is correctly write-only." -ForegroundColor Green