Skip to content

P03-T07 - HQ-FS01 File Services

Objective

Deploy a dedicated file server on VLAN 20, join it to the domain, and prepare SMB resources for workstation resource-access validation.

Validated lab status

Validated against the live lab on Friday, July 17, 2026:

  • HQ-FS01 was deployed and joined successfully.
  • The base shares Departments, Public, and Backups were created successfully.
  • The access model used later by workstation testing was validated from real domain-joined clients.
  • \\HQ-FS01\Public was validated with:
  • read access for general domain users
  • modify access through DL-HQ-FS01-Public-RW
  • successful write/delete by miguel.perez
  • denied write by ana.garcia

Validated boundary:

  • real client-side validation currently exists for \\HQ-FS01\Public
  • Departments\IT, Departments\Finance, and Backups currently remain validated here as server-side deployed baselines unless a later client runbook captures effective user validation for those paths

Inputs

Key Value
Source template 4000
Target VM HQ-FS01
Target VLAN 20
Static IP 172.20.20.21/24
Gateway 172.20.20.1
DNS 172.20.20.11

Target State

Property Value
Role File server
Domain membership Joined to corp.gntech.me
Shares Departmental shares plus backup staging

Prechecks

  • Complete P03-T01, P03-T02, P03-T03, P03-T04, P02-T07, and P03-T06.
  • Verify DNS resolution to HQ-DC01.
  • Confirm the server can reach corp.gntech.me.

Execution

  1. On HQ-DC01, create the file-service access groups by running the script locally:
.\Create-HQ-FS01-Groups.ps1
Show full FS01 group-creation script
param(
    [string]$GroupsOuDn,
    [switch]$ValidateOnly
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'

function Read-RequiredValue {
    param(
        [string]$Prompt,
        [string]$DefaultValue
    )

    $fullPrompt = $Prompt
    if ($DefaultValue) { $fullPrompt = "$Prompt [$DefaultValue]" }

    while ($true) {
        $value = Read-Host -Prompt $fullPrompt
        if (-not [string]::IsNullOrWhiteSpace($value)) { return $value.Trim() }
        if (-not [string]::IsNullOrWhiteSpace($DefaultValue)) { return $DefaultValue }
    }
}

function Resolve-Inputs {
    $script:GroupsOuDn = if ([string]::IsNullOrWhiteSpace($GroupsOuDn)) {
        Read-RequiredValue 'Groups OU DN' 'OU=Groups,OU=GNTECH,DC=corp,DC=gntech,DC=me'
    } else {
        $GroupsOuDn.Trim()
    }
}

function Ensure-Module {
    param([string]$Name)
    Import-Module $Name
}

function Ensure-Group {
    param(
        [string]$Name,
        [string]$Description
    )

    $existing = Get-ADGroup -LDAPFilter "(cn=$Name)" -SearchBase $script:GroupsOuDn -ErrorAction SilentlyContinue
    if ($existing) {
        return [pscustomobject]@{ Name = $Name; Action = 'Exists' }
    }

    if (-not $ValidateOnly) {
        New-ADGroup `
            -Name $Name `
            -SamAccountName $Name `
            -GroupScope DomainLocal `
            -GroupCategory Security `
            -Path $script:GroupsOuDn `
            -Description $Description | Out-Null
    }

    [pscustomobject]@{ Name = $Name; Action = $(if ($ValidateOnly) { 'Planned' } else { 'Created' }) }
}

Resolve-Inputs
Ensure-Module -Name ActiveDirectory

$definitions = @(
    @{ Name = 'DL-HQ-FS01-Public-RW'; Description = 'Modify access to HQ-FS01 Public share' },
    @{ Name = 'DL-HQ-FS01-Departments-IT-RW'; Description = 'Modify access to HQ-FS01 Departments IT folder' },
    @{ Name = 'DL-HQ-FS01-Departments-Finance-RW'; Description = 'Modify access to HQ-FS01 Departments Finance folder' },
    @{ Name = 'DL-HQ-FS01-Backups-RW'; Description = 'Modify access to HQ-FS01 Backups share' }
)

$summary = $definitions | ForEach-Object {
    [pscustomobject]@{
        Name = $_.Name
        Description = $_.Description
        Path = $script:GroupsOuDn
    }
}
$summary | Format-Table -AutoSize | Out-String | Write-Output

$results = foreach ($definition in $definitions) {
    Ensure-Group -Name $definition.Name -Description $definition.Description
}

$results | Format-Table -AutoSize | Out-String | Write-Output

if ($ValidateOnly) {
    Write-Output 'Validation mode only. HQ-FS01 groups were not created.'
} else {
    Write-Output 'HQ-FS01 groups completed successfully.'
}

Full reference:

  • Script Catalog
  • Script Source

  • Clone 4000 to HQ-FS01 on H1, attach the NIC to VLAN 20, and power on the VM.

  • Sign in directly to HQ-FS01 as the local built-in Administrator, then run the stage 1 script locally:

.\Deploy-HQ-FS01-Stage1.ps1
Show full FS01 Stage 1 script
param(
    [string]$TargetName,
    [string]$IPv4,
    [Nullable[int]]$PrefixLength,
    [string]$Gateway,
    [string[]]$DnsServers,
    [string]$DomainName,
    [string]$OuPath,
    [string]$JoinCredentialUser,
    [switch]$RestartIfNeeded
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'

function Assert-Elevated {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = [Security.Principal.WindowsPrincipal]::new($identity)

    if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
        throw 'Run this script from an elevated PowerShell session.'
    }
}

function Read-RequiredValue {
    param([string]$Prompt,[string]$DefaultValue)
    $fullPrompt = $Prompt
    if ($DefaultValue) { $fullPrompt = "$Prompt [$DefaultValue]" }
    while ($true) {
        $value = Read-Host -Prompt $fullPrompt
        if (-not [string]::IsNullOrWhiteSpace($value)) { return $value.Trim() }
        if (-not [string]::IsNullOrWhiteSpace($DefaultValue)) { return $DefaultValue }
    }
}

function Read-RequiredInt {
    param([string]$Prompt,[int]$DefaultValue)
    while ($true) {
        $value = Read-RequiredValue -Prompt $Prompt -DefaultValue $DefaultValue
        $parsed = 0
        if ([int]::TryParse($value, [ref]$parsed)) { return $parsed }
    }
}

function Read-DnsServerList {
    param([string[]]$DefaultServers)
    while ($true) {
        $value = Read-RequiredValue -Prompt 'DNS servers (comma-separated)' -DefaultValue ($DefaultServers -join ',')
        $servers = @($value.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        if ($servers.Count -gt 0) { return $servers }
    }
}

function Resolve-Inputs {
    $script:TargetName = if ([string]::IsNullOrWhiteSpace($TargetName)) { Read-RequiredValue 'Target hostname' 'HQ-FS01' } else { $TargetName.Trim() }
    $script:IPv4 = if ([string]::IsNullOrWhiteSpace($IPv4)) { Read-RequiredValue 'IPv4 address' '172.20.20.21' } else { $IPv4.Trim() }
    $script:PrefixLength = if ($null -eq $PrefixLength) { Read-RequiredInt 'IPv4 prefix length' 24 } else { [int]$PrefixLength }
    $script:Gateway = if ([string]::IsNullOrWhiteSpace($Gateway)) { Read-RequiredValue 'Default gateway' '172.20.20.1' } else { $Gateway.Trim() }
    $script:DnsServers = if ($DnsServers.Count -eq 0) { Read-DnsServerList -DefaultServers @('172.20.20.11') } else { @($DnsServers | ForEach-Object { $_.Trim() } | Where-Object { $_ }) }
    $script:DomainName = if ([string]::IsNullOrWhiteSpace($DomainName)) { Read-RequiredValue 'Domain FQDN' 'corp.gntech.me' } else { $DomainName.Trim() }
    $script:OuPath = if ([string]::IsNullOrWhiteSpace($OuPath)) { Read-RequiredValue 'Target server OU DN' 'OU=Servers,OU=GNTECH,DC=corp,DC=gntech,DC=me' } else { $OuPath.Trim() }
    $script:JoinCredentialUser = if ([string]::IsNullOrWhiteSpace($JoinCredentialUser)) { Read-RequiredValue 'Domain join user' 'corp.gntech.me\\svc.join.hq' } else { $JoinCredentialUser.Trim() }
}

function Get-PrimaryAdapter {
    $adapter = Get-NetAdapter | Where-Object Status -eq 'Up' | Sort-Object ifIndex | Select-Object -First 1
    if (-not $adapter) { throw 'No active network adapter was found.' }
    return $adapter
}

function Test-DomainReadiness {
    param([int]$InterfaceIndex)

    $dnsServers = (Get-DnsClientServerAddress -InterfaceIndex $InterfaceIndex -AddressFamily IPv4).ServerAddresses
    if (-not $dnsServers -or $dnsServers.Count -eq 0) {
        throw 'No IPv4 DNS servers are configured on the active adapter.'
    }

    try {
        Resolve-DnsName $script:DomainName -ErrorAction Stop | Out-Null
    }
    catch {
        throw "DNS validation failed for domain '$($script:DomainName)'."
    }
}

function Set-ExactDnsServers {
    param([int]$InterfaceIndex,[string[]]$DesiredServers)
    $current = (Get-DnsClientServerAddress -InterfaceIndex $InterfaceIndex -AddressFamily IPv4).ServerAddresses
    if (@($current) -join ',' -ne @($DesiredServers) -join ',') {
        Set-DnsClientServerAddress -InterfaceIndex $InterfaceIndex -ServerAddresses $DesiredServers
    }
}

function Set-ExactIPv4Configuration {
    param([int]$InterfaceIndex,[string]$DesiredIPv4,[int]$DesiredPrefixLength,[string]$DesiredGateway)
    $existing = Get-NetIPAddress -InterfaceIndex $InterfaceIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
    foreach ($address in $existing) {
        if ($address.IPAddress -eq $DesiredIPv4 -and $address.PrefixLength -eq $DesiredPrefixLength) { continue }
        if ($address.IPAddress -like '169.254.*') { continue }
        Remove-NetIPAddress -InterfaceIndex $InterfaceIndex -AddressFamily IPv4 -IPAddress $address.IPAddress -Confirm:$false -ErrorAction SilentlyContinue
    }
    $desiredAddress = Get-NetIPAddress -InterfaceIndex $InterfaceIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -eq $DesiredIPv4 -and $_.PrefixLength -eq $DesiredPrefixLength } | Select-Object -First 1
    if (-not $desiredAddress) {
        New-NetIPAddress -InterfaceIndex $InterfaceIndex -IPAddress $DesiredIPv4 -PrefixLength $DesiredPrefixLength | Out-Null
    }
    $defaultRoutes = Get-NetRoute -InterfaceIndex $InterfaceIndex -DestinationPrefix '0.0.0.0/0' -AddressFamily IPv4 -ErrorAction SilentlyContinue
    foreach ($route in $defaultRoutes) {
        if ($route.NextHop -ne $DesiredGateway) {
            Remove-NetRoute -InterfaceIndex $InterfaceIndex -DestinationPrefix '0.0.0.0/0' -NextHop $route.NextHop -Confirm:$false -ErrorAction SilentlyContinue
        }
    }
    $desiredRoute = Get-NetRoute -InterfaceIndex $InterfaceIndex -DestinationPrefix '0.0.0.0/0' -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object NextHop -eq $DesiredGateway | Select-Object -First 1
    if (-not $desiredRoute) {
        New-NetRoute -InterfaceIndex $InterfaceIndex -DestinationPrefix '0.0.0.0/0' -NextHop $DesiredGateway -RouteMetric 256 | Out-Null
    }
}

Resolve-Inputs
Assert-Elevated

$adapter = Get-PrimaryAdapter
Set-ExactIPv4Configuration -InterfaceIndex $adapter.ifIndex -DesiredIPv4 $script:IPv4 -DesiredPrefixLength $script:PrefixLength -DesiredGateway $script:Gateway
Set-ExactDnsServers -InterfaceIndex $adapter.ifIndex -DesiredServers $script:DnsServers
Set-DnsClient -InterfaceIndex $adapter.ifIndex -ConnectionSpecificSuffix $script:DomainName -RegisterThisConnectionsAddress $true -UseSuffixWhenRegistering $true
Test-DomainReadiness -InterfaceIndex $adapter.ifIndex

$computerSystem = Get-CimInstance Win32_ComputerSystem
$renameRequired = $env:COMPUTERNAME -ne $script:TargetName
$joinRequired = -not $computerSystem.PartOfDomain
$restartRequired = $false

if ($renameRequired -or $joinRequired) {
    $credential = Get-Credential -UserName $script:JoinCredentialUser -Message 'Enter domain join credential for HQ-FS01'
    if ($renameRequired) {
        Add-Computer -DomainName $script:DomainName -Credential $credential -OUPath $script:OuPath -NewName $script:TargetName -Force
        $restartRequired = $true
        Write-Output 'The server was renamed and joined to the domain.'
    } else {
        Add-Computer -DomainName $script:DomainName -Credential $credential -OUPath $script:OuPath -Force
        $restartRequired = $true
        Write-Output 'The server was joined to the domain.'
    }
} else {
    Write-Output 'No rename or domain join action is required.'
}

Write-Output "TargetName: $($script:TargetName)"
Write-Output "IPv4: $($script:IPv4)/$($script:PrefixLength)"
Write-Output "Gateway: $($script:Gateway)"
Write-Output "DnsServers: $($script:DnsServers -join ', ')"
Write-Output "DomainValidation: OK"

if ($restartRequired -and $RestartIfNeeded) {
    Restart-Computer -Force
} elseif ($restartRequired) {
    Write-Output 'A restart is required to complete HQ-FS01 stage 1.'
}

Full reference:

This script:

  • prompts for hostname, IP, gateway, DNS, and domain join values if not passed as parameters
  • renames the server, applies the static VLAN 20 network baseline, and validates DNS reachability
  • prompts securely for join credentials instead of storing them in the repository
  • joins the server to the domain and can restart automatically if requested

Operational note:

  • this script must be run from an elevated PowerShell session
  • the script now fails before the join if domain DNS validation is not working

  • After the domain join reboot, sign in with a domain admin account and run the share-configuration script locally on HQ-FS01:

.\Configure-HQ-FS01-Shares.ps1

Full reference:

This script installs the file server role if needed, creates the validated base share paths, creates the Departments, Public, and Backups SMB shares, and applies the tested NTFS/share ACL baseline.

Show full share-configuration script
param(
    [string]$ShareRoot,
    [string]$DomainNetbios,
    [switch]$ValidateOnly
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'

function Assert-Elevated {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
        throw 'Run this script from an elevated PowerShell session.'
    }
}

function Read-RequiredValue {
    param([string]$Prompt,[string]$DefaultValue)
    $fullPrompt = $Prompt
    if ($DefaultValue) { $fullPrompt = "$Prompt [$DefaultValue]" }
    while ($true) {
        $value = Read-Host -Prompt $fullPrompt
        if (-not [string]::IsNullOrWhiteSpace($value)) { return $value.Trim() }
        if (-not [string]::IsNullOrWhiteSpace($DefaultValue)) { return $DefaultValue }
    }
}

function Resolve-Inputs {
    $script:ShareRoot = if ([string]::IsNullOrWhiteSpace($ShareRoot)) { Read-RequiredValue 'Share root path' 'C:\Shares' } else { $ShareRoot.Trim() }
    $script:DomainNetbios = if ([string]::IsNullOrWhiteSpace($DomainNetbios)) { Read-RequiredValue 'Domain NetBIOS name' 'GNTECH' } else { $DomainNetbios.Trim() }
}

function Ensure-Directory {
    param([string]$Path)
    if (-not (Test-Path -LiteralPath $Path)) {
        if (-not $ValidateOnly) { New-Item -Path $Path -ItemType Directory -Force | Out-Null }
        if ($ValidateOnly) { return 'Planned' }
        return 'Created'
    }
    'Exists'
}

function Set-FolderAcl {
    param([string]$Path,[array]$Rules)
    $acl = New-Object System.Security.AccessControl.DirectorySecurity
    $acl.SetAccessRuleProtection($true, $false)
    $inheritanceFlags = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
    $propagationFlags = [System.Security.AccessControl.PropagationFlags]::None
    foreach ($rule in $Rules) {
        $fsRights = [System.Security.AccessControl.FileSystemRights]$rule.Rights
        $accessType = [System.Security.AccessControl.AccessControlType]::Allow
        $aclRule = New-Object System.Security.AccessControl.FileSystemAccessRule($rule.Identity,$fsRights,$inheritanceFlags,$propagationFlags,$accessType)
        $acl.AddAccessRule($aclRule) | Out-Null
    }
    if (-not $ValidateOnly) { Set-Acl -Path $Path -AclObject $acl }
    if ($ValidateOnly) { 'Planned' } else { 'Applied' }
}

function Ensure-SmbShareState {
    param([string]$Name,[string]$Path,[string]$Description,[string[]]$FullAccess,[string[]]$ChangeAccess,[string[]]$ReadAccess,[string]$FolderEnumerationMode = 'AccessBased')
    $existing = Get-SmbShare -Name $Name -ErrorAction SilentlyContinue
    if ($existing -and $existing.Path -ne $Path) {
        throw "SMB share '$Name' exists at '$($existing.Path)' instead of '$Path'. Correct it manually before rerunning."
    }
    if (-not $ValidateOnly) {
        if (-not $existing) {
            $splat = @{ Name = $Name; Path = $Path; Description = $Description; FolderEnumerationMode = $FolderEnumerationMode }
            if ($FullAccess) { $splat.FullAccess = $FullAccess }
            if ($ChangeAccess) { $splat.ChangeAccess = $ChangeAccess }
            if ($ReadAccess) { $splat.ReadAccess = $ReadAccess }
            New-SmbShare @splat | Out-Null
        }
        else {
            Set-SmbShare -Name $Name -Description $Description -FolderEnumerationMode $FolderEnumerationMode -Force | Out-Null
            $desiredAccess = @()
            $desiredAccess += @($FullAccess | ForEach-Object { @{ Account = $_; Right = 'Full' } })
            $desiredAccess += @($ChangeAccess | ForEach-Object { @{ Account = $_; Right = 'Change' } })
            $desiredAccess += @($ReadAccess | ForEach-Object { @{ Account = $_; Right = 'Read' } })
            foreach ($entry in $desiredAccess) {
                Grant-SmbShareAccess -Name $Name -AccountName $entry.Account -AccessRight $entry.Right -Force | Out-Null
            }
        }
    }
    if ($ValidateOnly) { if ($existing) { 'Existing path validated' } else { 'Planned' } }
    elseif ($existing) { 'Reconciled' } else { 'Created' }
}

function Assert-AccountResolvable {
    param([string[]]$Identity)
    foreach ($item in $Identity) {
        try { ([System.Security.Principal.NTAccount]$item).Translate([System.Security.Principal.SecurityIdentifier]) | Out-Null }
        catch { throw "Required account or group '$item' cannot be resolved on this server." }
    }
}

Resolve-Inputs
Assert-Elevated

$departmentsRoot = Join-Path $script:ShareRoot 'Departments'
$publicRoot = Join-Path $script:ShareRoot 'Public'
$backupsRoot = Join-Path $script:ShareRoot 'Backups'
$itRoot = Join-Path $departmentsRoot 'IT'
$financeRoot = Join-Path $departmentsRoot 'Finance'

$domainUsers = "$($script:DomainNetbios)\Domain Users"
$domainAdmins = "$($script:DomainNetbios)\Domain Admins"
$builtinAdmins = 'BUILTIN\Administrators'
$systemAccount = 'NT AUTHORITY\SYSTEM'

Assert-AccountResolvable -Identity @(
    $domainUsers,$domainAdmins,$builtinAdmins,$systemAccount,
    "$($script:DomainNetbios)\DL-HQ-FS01-Departments-IT-RW",
    "$($script:DomainNetbios)\DL-HQ-FS01-Departments-Finance-RW",
    "$($script:DomainNetbios)\DL-HQ-FS01-Public-RW",
    "$($script:DomainNetbios)\DL-HQ-FS01-Backups-RW"
)

$feature = Get-WindowsFeature FS-FileServer
if (-not $feature.Installed -and -not $ValidateOnly) { Install-WindowsFeature FS-FileServer | Out-Null }
Write-Output "FS-FileServer: $(if ($feature.Installed) { 'Installed' } elseif ($ValidateOnly) { 'Missing' } else { 'Installed now' })"

$folders = @($script:ShareRoot,$departmentsRoot,$publicRoot,$backupsRoot,$itRoot,$financeRoot)
$folderResults = foreach ($folder in $folders) {
    [pscustomobject]@{ Path = $folder; Action = (Ensure-Directory -Path $folder) }
}
$folderResults | Format-Table -AutoSize | Out-String | Write-Output

Set-FolderAcl -Path $departmentsRoot -Rules @(
    @{ Identity = $systemAccount; Rights = 'FullControl' },
    @{ Identity = $builtinAdmins; Rights = 'FullControl' },
    @{ Identity = $domainAdmins; Rights = 'FullControl' },
    @{ Identity = $domainUsers; Rights = 'ReadAndExecute, Synchronize' }
)
Set-FolderAcl -Path $itRoot -Rules @(
    @{ Identity = $systemAccount; Rights = 'FullControl' },
    @{ Identity = $builtinAdmins; Rights = 'FullControl' },
    @{ Identity = $domainAdmins; Rights = 'FullControl' },
    @{ Identity = "$($script:DomainNetbios)\DL-HQ-FS01-Departments-IT-RW"; Rights = 'Modify, Synchronize' }
)
Set-FolderAcl -Path $financeRoot -Rules @(
    @{ Identity = $systemAccount; Rights = 'FullControl' },
    @{ Identity = $builtinAdmins; Rights = 'FullControl' },
    @{ Identity = $domainAdmins; Rights = 'FullControl' },
    @{ Identity = "$($script:DomainNetbios)\DL-HQ-FS01-Departments-Finance-RW"; Rights = 'Modify, Synchronize' }
)
Set-FolderAcl -Path $publicRoot -Rules @(
    @{ Identity = $systemAccount; Rights = 'FullControl' },
    @{ Identity = $builtinAdmins; Rights = 'FullControl' },
    @{ Identity = $domainAdmins; Rights = 'FullControl' },
    @{ Identity = $domainUsers; Rights = 'ReadAndExecute, Synchronize' },
    @{ Identity = "$($script:DomainNetbios)\DL-HQ-FS01-Public-RW"; Rights = 'Modify, Synchronize' }
)
Set-FolderAcl -Path $backupsRoot -Rules @(
    @{ Identity = $systemAccount; Rights = 'FullControl' },
    @{ Identity = $builtinAdmins; Rights = 'FullControl' },
    @{ Identity = $domainAdmins; Rights = 'FullControl' },
    @{ Identity = "$($script:DomainNetbios)\DL-HQ-FS01-Backups-RW"; Rights = 'Modify, Synchronize' }
)

$shareResults = @(
    [pscustomobject]@{
        Name = 'Departments'
        Action = (Ensure-SmbShareState -Name 'Departments' -Path $departmentsRoot -Description 'Departmental data root' -FullAccess @($domainAdmins) -ReadAccess @($domainUsers))
    },
    [pscustomobject]@{
        Name = 'Public'
        Action = (Ensure-SmbShareState -Name 'Public' -Path $publicRoot -Description 'Broad collaboration share' -FullAccess @($domainAdmins) -ChangeAccess @("$($script:DomainNetbios)\DL-HQ-FS01-Public-RW") -ReadAccess @($domainUsers))
    },
    [pscustomobject]@{
        Name = 'Backups'
        Action = (Ensure-SmbShareState -Name 'Backups' -Path $backupsRoot -Description 'Backup staging share' -FullAccess @($domainAdmins) -ChangeAccess @("$($script:DomainNetbios)\DL-HQ-FS01-Backups-RW"))
    }
)
$shareResults | Format-Table -AutoSize | Out-String | Write-Output

if ($ValidateOnly) {
    Write-Output 'Validation mode only. HQ-FS01 share configuration was not applied.'
} else {
    Write-Output 'HQ-FS01 share configuration completed successfully.'
}

Preflight and re-run behavior:

  • run -ValidateOnly first to resolve all required domain groups, inspect planned folders/shares, and confirm the role state without installing the role or changing ACLs
  • normal execution stops if an existing share name points to a different path
  • a normal re-run reapplies the authoritative NTFS baseline and reconciles the required share grants
  • NTFS inheritance is intentionally disabled on the managed business folders; rerunning replaces their explicit ACLs with the documented baseline, so place application-specific ACL changes in a separately reviewed extension rather than directly on these managed roots

Runbook boundary:

  • this runbook validates server-side file-service preparation on HQ-FS01
  • this runbook does not by itself close end-user access validation for every share path
  • effective workstation access must be validated from real domain-joined clients in the dependent client runbooks

Validated access model for the base shares:

  • Departments
  • share level: Domain Admins = Full, Domain Users = Read
  • NTFS root: Domain Users = Read
  • subfolders such as IT and Finance are writable only through the matching DL-HQ-FS01-* groups
  • Public
  • share level: Domain Admins = Full, DL-HQ-FS01-Public-RW = Change, Domain Users = Read
  • NTFS: Domain Users = Read, DL-HQ-FS01-Public-RW = Modify
  • Backups
  • share level: Domain Admins = Full, DL-HQ-FS01-Backups-RW = Change
  • NTFS: DL-HQ-FS01-Backups-RW = Modify

Validation scope for this runbook:

  • Public is the share path later validated from real clients in P03-T08
  • Departments\IT, Departments\Finance, and Backups are validated here as configured server-side baselines unless a later client-validation runbook proves effective access from a real user session
  • do not describe the non-Public share paths as end-user validated unless that evidence is added later

Validation

  • HQ-FS01 resolves and pings HQ-DC01.
  • Domain join completes without trust errors.
  • Get-SmbShare shows Departments, Public, and Backups.
  • Get-SmbShareAccess -Name Public shows DL-HQ-FS01-Public-RW with change access and Domain Users with read access.
  • Get-ADGroupMember 'DL-HQ-FS01-Public-RW' confirms the approved write members.
  • HQ-FS01 is domain joined and ready for client access validation in the next runbook.
  • server-side share and ACL validation completes here; end-user access validation continues in P03-T08.
  • evidence for this runbook must distinguish server-side deployment success from real client-side authorization success

Read-only preflight:

.\Configure-HQ-FS01-Shares.ps1 -ValidateOnly

Evidence

  • Get-SmbShare
  • Get-SmbShareAccess -Name Public
  • Get-ADGroupMember 'DL-HQ-FS01-Public-RW'
  • Get-ADComputer HQ-FS01
  • Screenshot only if required by handoff

Rollback

  • Remove the clone and rebuild from template if join or storage baseline is incorrect.
  • Remove incorrect shares and ACLs only if the server baseline is otherwise sound.