BUILD / INSPECT / UNDERSTAND

Infrastructure Lab.

A workspace for exploring systems, tracing connections and reviewing the work behind them.

SELECT A PROJECT

Inside the lab.

Seven documented projects and six automation tools. Explore diagrams and source here, then download a project pack when you want to work locally.

7 projects shown

Sample automation and policy logic are tested with fictional data. Server, router and tenant configurations remain documented walkthroughs until deployed in an authorised lab.

IDENTITY / DNS / GROUP POLICY

Active Directory Lab

Configuration walkthrough · infrastructure validation requiredDownload project pack ↓

01 / Objective

Build an isolated domain and trace how a user moves from account provisioning to a managed Windows desktop.

One Windows Server VM, one domain-capable Windows client, an isolated virtual switch and authorised installation media. Use lab.example and synthetic users.

02 / Architecture

Windows client / Lab DNS / Domain controller / OU + policyWindows clientLab DNSDomain controllerOU + policy
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Establish the baseline

    Create disposable VMs, record their allocations and set a static address on the domain controller. Ensure the client uses the lab DNS server, not a public resolver.

  2. Deploy the directory

    Use the supplied AD walkthrough to install AD DS and create the isolated forest. Review prerequisite checks and enter the recovery password interactively.

  3. Model user access

    Create a Lab OU with Admin, Staff and Students child OUs. Generate the onboarding plan from synthetic CSV data, review it, then create only the accounts needed for the exercise.

  4. Apply a scoped policy

    Link one harmless test policy to the Lab OU. Join the client, sign in with a test user and inspect effective policy with gpresult /r.

  5. Test a failure

    Temporarily point the disposable client at an incorrect DNS server. Record the failure, restore lab DNS and repeat the domain lookup before retrying the join.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

configs/ad-walkthrough.txt

Download this file ↓
# Isolated Windows Server lab only. Review and execute individually.

# Prerequisites: licensed/evaluation media, local admin session, isolated switch.

# Configure a static lab IP and DNS plan before promotion. No secrets are stored.

Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools
Install-ADDSForest -DomainName 'lab.example' -InstallDNS
# Promotion prompts for the DSRM password and normally restarts the server.

# After restart, use an authorised domain administrator session:

Get-ADDomain
Get-ADDomainController -Filter *
Resolve-DnsName -Type SRV '_ldap._tcp.dc._msdcs.lab.example'
# Create Lab OU and child OUs Admin, Staff, Students with management tools.

# Link a harmless test GPO ONLY to the Lab OU. Do not change Default Domain Policy.

# Join one disposable Windows client using its own lab DNS setting.

# On the client:

whoami
gpresult /r
# Evidence: capture redacted OU structure, domain join and effective policy.

# Rollback: revert or remove disposable lab VMs, never production controllers.

scripts/onboarding-plan.ps1

Download this file ↓
# Build a reviewable onboarding plan from synthetic input. Does not create accounts.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-users.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
$departments = @('Admin','Staff','Students')
foreach ($user in (Import-Csv -LiteralPath $InputPath)) {
    $account = ([string]$user.Account).Trim().ToLowerInvariant()
    if ($account -notmatch '^[a-z][a-z0-9.-]{2,19}$') { throw "Invalid sample account: $account" }
    if ($seen.ContainsKey($account)) { throw "Duplicate account: $account" }
    if ($departments -notcontains $user.Department) { throw "Unknown department: $($user.Department)" }
    if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { throw 'DisplayName is required.' }
    $seen[$account] = $true
    [pscustomobject]@{
        Account = $account
        DisplayName = $user.DisplayName
        UserPrincipalName = "$account@lab.example"
        Department = $user.Department
        TargetOU = "OU=$($user.Department),OU=Lab,DC=lab,DC=example"
        Action = 'Review only - no account created'
    }
}

data/sample-users.csv

Download this file ↓
Account,DisplayName,Department
alex.sample,Alex Sample,Staff
sam.demo,Sam Demo,Admin
taylor.lab,Taylor Lab,Students

05 / Result & evidence

Expected infrastructure result: a joined client with working directory lookup and correctly scoped policy. The onboarding planner is executable using the supplied sample CSV; no accounts are created by that script.

Sample accountDepartmentProposed UPN
alex.sampleStaffalex.sample@lab.example
sam.demoAdminsam.demo@lab.example
taylor.labStudentstaylor.lab@lab.example
Actual onboarding-plan.ps1 output. Review-only proposals; no accounts created.
06 / Validation & troubleshooting
  • SRV lookup returns the lab domain controller.
  • The synthetic user can sign in to the joined client.
  • The test GPO is applied only to the intended OU.
  • A DNS fault is distinguishable from an authentication failure.
SymptomInvestigation
Domain cannot be locatedCheck client DNS, server address and the SRV lookup before changing the account.
Policy missingCheck OU placement, security filtering and gpresult output; distinguish user and computer policy.

Screenshots & capture guide

Capture the Lab OU hierarchy, redacted DNS SRV lookup and client gpresult output after a real run.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • DNS is a dependency of directory discovery, not a separate afterthought.
  • OU design and GPO scope should be checked from the endpoint.
  • Keep recovery credentials out of scripts, screenshots and source control.
Microsoft: Install-ADDSForest ↗

SEGMENTATION / LAYER 3 / CONTROLLED ACCESS

VLAN, Routing & Access Policy

Tested sample logic · isolated infrastructure steps documentedDownload project pack ↓

01 / Objective

Separate Admin, Staff, Students and Servers, then prove that routing and access control are different layers of the design.

An isolated IOS-style simulator with a Layer 3 core, access switch and test endpoints. Four /26 networks divide 192.0.2.0/24; no employer addresses are used.

02 / Architecture

Access endpoints / Access switch / Layer 3 core / Server VLANLayer 3 coreAdmin / VLAN 10Staff / VLAN 20Students / VLAN 30
Endpoint VLANs connect through the core; a separate server VLAN hosts shared services.

INTERACTIVE / SYNTHETIC INPUTS

Trace a network decision

Choose a source, service and destination. This local policy model sends no traffic and does not simulate a complete firewall or Active Directory protocol stack.

READY · local policy demonstrator

DNS to Identity and HTTPS to Cloud are permitted for all sample segments. RDP to Identity is permitted only for Admin. Other combinations are denied.

EndpointAccess switchCoreDestination
03 / Configuration walkthrough
  1. Plan the address space

    Assign gateways 192.0.2.1, .65, .129 and .193 to VLANs 10, 20, 30 and 40. Keep host addresses inside each subnet and outside its network/broadcast addresses.

  2. Build Layer 2

    Create VLANs, assign access ports and allow only the required VLANs on the trunk. Ensure at least one relevant interface is active for each SVI.

  3. Enable routing

    Apply the core template after adapting interface names. Configure endpoint masks and gateways, then verify connected routes and gateway reachability.

  4. Define policy separately

    Use the browser policy demonstrator below to review permitted flows. The downloadable core template provides routing only; implement reviewed rules in the lab firewall or ACL layer before claiming isolation.

  5. Validate both outcomes

    Test allowed DNS/HTTPS and an intentionally denied remote-management flow. Record the destination, port and source segment for every result.

SegmentSubnetGateway
Admin / 10192.0.2.0/26192.0.2.1
Staff / 20192.0.2.64/26192.0.2.65
Students / 30192.0.2.128/26192.0.2.129
Servers / 40192.0.2.192/26192.0.2.193
04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

configs/vlan-core.txt

Download this file ↓
! Reference template for an isolated Cisco IOS-style Layer 3 switch.

! Review interface names and feature support for your simulator/image.

! Not production configuration. VLAN interfaces need an active port/trunk.

hostname LAB-CORE
ip routing
vlan 10
 name ADMIN
vlan 20
 name STAFF
vlan 30
 name STUDENTS
vlan 40
 name SERVERS
interface Vlan10
 ip address 192.0.2.1 255.255.255.192
 no shutdown
interface Vlan20
 ip address 192.0.2.65 255.255.255.192
 no shutdown
interface Vlan30
 ip address 192.0.2.129 255.255.255.192
 no shutdown
interface Vlan40
 ip address 192.0.2.193 255.255.255.192
 no shutdown
interface GigabitEthernet0/1
 description SAMPLE-ACCESS-SWITCH
 switchport mode trunk
 switchport trunk allowed vlan 10,20,30,40
 no shutdown
!

! Routing only; no access restriction is implied by this template.

! Apply a separately reviewed policy in the firewall or ACL exercise.

! Verify:

! show vlan brief

! show interfaces trunk

! show ip interface brief

! show ip route connected

scripts/connectivity.ps1

Download this file ↓
# Sample: one authorised destination, one TCP port. No scanning or stored secrets.

[CmdletBinding()]
param(
    [ValidateNotNullOrEmpty()][string]$ComputerName = 'localhost',
    [ValidateRange(1,65535)][int]$Port = 443
)
$result = Test-NetConnection -ComputerName $ComputerName -Port $Port -WarningAction SilentlyContinue
[pscustomobject]@{
    Target = $ComputerName
    Port = $Port
    TcpReachable = [bool]$result.TcpTestSucceeded
}
# TCP reachability does not verify TLS, authentication or application health.

network-engine.js

Download this file ↓
(function(scope) {
  'use strict';
  const sources = ['Admin','Staff','Students'];
  const targets = ['Identity','Cloud'];
  const services = ['DNS','HTTPS','RDP'];
  function evaluate(source,target,service,linkUp=true) {
    if(!sources.includes(source)||!targets.includes(target)||!services.includes(service)) throw new Error('Unknown lab input');
    const route=[source+' endpoint','Access switch','Core switch',target==='Cloud'?'Edge gateway':'Server segment',target];
    if(!linkUp) return {allowed:false,reason:'Core uplink is down. Connectivity fails before access policy can be evaluated.',route,stage:'link'};
    const allowed=(target==='Identity'&&service==='DNS')||(target==='Cloud'&&service==='HTTPS')||(source==='Admin'&&target==='Identity'&&service==='RDP');
    return {allowed,route,stage:'policy',reason:allowed?(service==='RDP'?'Only the Admin segment has a sample RDP permit to the identity server.':'The selected flow matches an explicit permit in the sample policy.'):'No matching permit. The sample policy denies this flow by default.'};
  }
  const api={evaluate,sources,targets,services};
  if(typeof module==='object'&&module.exports) module.exports=api;
  else scope.LabNetwork=api;
})(typeof window==='undefined'?globalThis:window);

05 / Result & evidence

The interactive demonstrator evaluates a documented sample policy locally in your browser. It does not contact a network. Device configuration and reachability still require validation in your simulator.

SourceDestinationServiceSample policy
All segmentsIdentityDNSPermit
All segmentsCloudHTTPSPermit
AdminIdentityRDPPermit
Staff / StudentsIdentityRDPDeny
AnyAnyUnlisted flowDeny
Policy specification used by the tested browser demonstrator. Not device output.
06 / Validation & troubleshooting
  • Access ports are in their intended VLANs and the trunk carries only approved VLANs.
  • Each endpoint reaches its own gateway.
  • Routing-only configuration is explicitly distinguished from enforced access restrictions.
  • The policy demonstrator denies unlisted flows and blocks traffic when the sample uplink is down.
SymptomInvestigation
SVI stays downCheck VLAN existence and active access or trunk membership.
Gateway works, service does notCheck the target listener, return route and policy; do not assume every failure is a firewall issue.

Screenshots & capture guide

The live browser policy result below is an actual calculation over synthetic inputs. Capture simulator VLAN, trunk and route tables separately after deploying the template.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • VLANs separate broadcast domains; they do not automatically restrict routed traffic.
  • A blocked TCP probe alone does not identify whether routing, policy or the service caused the failure.
  • Document default-deny behaviour and explicit exceptions before implementing rules.

AREA 0 / ADJACENCIES / ROUTE LEARNING

OSPF Routing & Fault Diagnosis

Configuration walkthrough · infrastructure validation requiredDownload project pack ↓

01 / Objective

Build a two-router adjacency, exchange loopback routes and document what changes when the only transit link fails.

Two isolated IOS-style routers, transit network 198.51.100.0/30 and loopbacks 192.0.2.1/32 and 203.0.113.1/32. This is a single-path exercise, not a redundant design.

02 / Architecture

R1 loopback / R1 / Area 0 / R2 / Area 0 / R2 loopbackR1 loopbackR1 / Area 0R2 / Area 0R2 loopback
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Bring up transit

    Configure .1 and .2 on the /30 transit interfaces. Confirm direct reachability before enabling a routing protocol.

  2. Enable OSPF

    Use unique router IDs, Area 0 and passive-interface default. Enable neighbour discovery only on the transit interface.

  3. Verify learned routes

    Check neighbour state and OSPF-learned /32 routes. Test loopback reachability with an appropriate source address.

  4. Introduce a controlled fault

    Disable the disposable lab transit interface and record the loss of connectivity. Restore it and observe adjacency and route recovery.

  5. Explain the limitation

    There is no alternate path in this topology. Add a third router as a separate extension before investigating path selection or failover.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

configs/ospf-routers.txt

Download this file ↓
! Two isolated IOS-style routers. Adapt interface names before use.

! Documentation networks; no connection to an employer network.

! ROUTER A

hostname LAB-R1
interface GigabitEthernet0/0
 ip address 198.51.100.1 255.255.255.252
 no shutdown
interface Loopback0
 ip address 192.0.2.1 255.255.255.255
router ospf 10
 router-id 1.1.1.1
 passive-interface default
 no passive-interface GigabitEthernet0/0
 network 198.51.100.0 0.0.0.3 area 0
 network 192.0.2.1 0.0.0.0 area 0
!

! ROUTER B - apply this section only on the second router

hostname LAB-R2
interface GigabitEthernet0/0
 ip address 198.51.100.2 255.255.255.252
 no shutdown
interface Loopback0
 ip address 203.0.113.1 255.255.255.255
router ospf 10
 router-id 2.2.2.2
 passive-interface default
 no passive-interface GigabitEthernet0/0
 network 198.51.100.0 0.0.0.3 area 0
 network 203.0.113.1 0.0.0.0 area 0
!

! VERIFY on each router:

! show ip ospf neighbor

! show ip route ospf

! show ip ospf interface GigabitEthernet0/0

! Single uplink: link failure interrupts this topology. No failover is claimed.

05 / Result & evidence

Expected result: two routers exchange their loopback routes. A single-link outage interrupts the path. No convergence time or high-availability metric is claimed without an actual capture.

CheckExpected observation
Lab acceptance criterionNeighbour state reaches FULL on the transit link.
Lab acceptance criterionEach router learns the remote loopback through OSPF.
Lab acceptance criterionLoopback interfaces do not attempt to form adjacencies.
Lab acceptance criterionTransit failure removes the only usable path; restoration re-establishes reachability.
Expected observations for a future infrastructure run; not captured device output.
06 / Validation & troubleshooting
  • Neighbour state reaches FULL on the transit link.
  • Each router learns the remote loopback through OSPF.
  • Loopback interfaces do not attempt to form adjacencies.
  • Transit failure removes the only usable path; restoration re-establishes reachability.
SymptomInvestigation
No neighbourCheck subnet, Area ID, interface state, timers and passive-interface settings.
Neighbour exists but route missingCheck that the loopback network is included and inspect the routing table, not just neighbour state.

Screenshots & capture guide

Capture neighbour state, the learned route and before/after outage observations from your own simulator.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • Verify Layer 3 reachability before debugging adjacency state.
  • Passive interfaces can advertise networks without sending neighbour-discovery traffic there.
  • Dynamic routing does not create redundancy where no alternate path exists.
Cisco: Default Passive Interfaces ↗

ENTRA ID / LICENCES / ACCESS REVIEW

Microsoft 365 Lifecycle & Licensing

Tested sample logic · isolated infrastructure steps documentedDownload project pack ↓

01 / Objective

Document an account lifecycle and turn sanitised licence inventory into a capacity report without embedding tenant information.

The offline report runs with the included fictional CSV. A real lifecycle exercise requires your own authorised test tenant, suitable test licences and the necessary delegated permissions.

02 / Architecture

Synthetic user / Entra identity / Service entitlement / Access reviewSynthetic userEntra identityService entitlementAccess review
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Separate the three concerns

    Track account state, licence assignment and resource permissions individually. A licence does not prove access to every resource.

  2. Run the offline report

    Use licence-report.ps1 against sample-licences.csv. Inspect a SKU with spare units, one at capacity and one deliberately overallocated.

  3. Prepare a test-tenant checklist

    Define the sample user's role, approved services and expected resource access. Keep identifiers and authentication out of the downloadable project.

  4. Validate the lifecycle

    In an authorised test tenant, provision one synthetic user and verify service access. Record offboarding checks according to the test policy.

  5. Review capacity exceptions

    Do not silently clamp negative available units to zero. Preserve the discrepancy and investigate what the inventory means.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

scripts/licence-report.ps1

Download this file ↓
# Offline capacity report. No sign-in or tenant access; input is a sanitised CSV.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-licences.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
foreach ($sku in (Import-Csv -LiteralPath $InputPath)) {
    $enabled = 0; $consumed = 0
    if ([string]::IsNullOrWhiteSpace($sku.Sku)) { throw 'Sku is required.' }
    if ($seen.ContainsKey($sku.Sku)) { throw "Duplicate SKU: $($sku.Sku)" }
    $seen[$sku.Sku] = $true
    if (-not [int]::TryParse($sku.Enabled,[ref]$enabled) -or $enabled -lt 0) { throw 'Enabled must be a non-negative integer.' }
    if (-not [int]::TryParse($sku.Consumed,[ref]$consumed) -or $consumed -lt 0) { throw 'Consumed must be a non-negative integer.' }
    $available = $enabled - $consumed
    [pscustomobject]@{
        Sku = $sku.Sku; Enabled = $enabled; Consumed = $consumed
        Available = $available
        Status = if ($available -lt 0) { 'Overallocated - review' } elseif ($available -eq 0) { 'At capacity' } else { 'Available' }
    }
}

data/sample-licences.csv

Download this file ↓
Sku,Enabled,Consumed
DEMO_COLLAB,25,21
DEMO_MAIL,10,10
DEMO_REVIEW,5,6

05 / Result & evidence

The offline report calculates capacity from fictional data. Live Microsoft 365 provisioning, service activation and offboarding are separate validation steps, not completed claims.

Sample SKUEnabledConsumedAvailableResult
DEMO_COLLAB25214Available
DEMO_MAIL10100At capacity
DEMO_REVIEW56-1Overallocated - review
Actual licence-report.ps1 output using fictional CSV input.
06 / Validation & troubleshooting
  • Available units equal enabled minus consumed units.
  • At-capacity and overallocated examples remain visibly distinct.
  • Malformed counts and duplicate SKU rows are rejected.
  • The sample report does not connect to Microsoft Graph or change licences.
SymptomInvestigation
Negative availabilityReview the inventory and subscription state; retain the exception in the report.
Report rejects dataCheck required headers, duplicate SKUs and integer counts before changing the script.

Screenshots & capture guide

A tested fictional report is shown below. Tenant screenshots and real user inventory are intentionally excluded.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • Inventory reports should expose discrepancies rather than hiding them.
  • Entitlement counts are not a substitute for user-level access review.
  • An authorised Graph inventory can provide subscribed SKU data; this public example uses a sanitised CSV instead.
Microsoft: Get-MgSubscribedSku ↗

ONBOARDING / MONITORING / STRUCTURED OUTPUT

PowerShell Operational Toolkit

Tested sample logic · isolated infrastructure steps documentedDownload project pack ↓

01 / Objective

Build small, reviewable tools that return useful objects, validate input and make their side effects explicit.

PowerShell plus the supplied CSV fixtures for offline scripts. Disk, service and TCP checks require the relevant Windows modules and should target only systems you are authorised to inspect.

02 / Architecture

Input / target / Validate / Read-only query / Structured reportInput / targetValidateRead-only queryStructured report
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Generate an onboarding plan

    Read the fictional user CSV, validate account names and department mappings, and emit proposed UPNs and OUs. No account-creation command is included.

  2. Monitor capacity

    Use disk-space.ps1 locally with a chosen warning threshold. Treat a below-threshold result as a review signal, not an automatic remediation trigger.

  3. Report service health

    Query an explicit set of local services. Distinguish observed state from unavailable queries, and compare service state with its intended baseline.

  4. Check one network service

    Use connectivity.ps1 with an authorised host and port. A successful TCP handshake does not prove TLS or application health.

  5. Test failure cases

    Exercise duplicate accounts, invalid department names and malformed inventory. Keep output as objects so a caller can format or export it later.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

scripts/onboarding-plan.ps1

Download this file ↓
# Build a reviewable onboarding plan from synthetic input. Does not create accounts.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-users.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
$departments = @('Admin','Staff','Students')
foreach ($user in (Import-Csv -LiteralPath $InputPath)) {
    $account = ([string]$user.Account).Trim().ToLowerInvariant()
    if ($account -notmatch '^[a-z][a-z0-9.-]{2,19}$') { throw "Invalid sample account: $account" }
    if ($seen.ContainsKey($account)) { throw "Duplicate account: $account" }
    if ($departments -notcontains $user.Department) { throw "Unknown department: $($user.Department)" }
    if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { throw 'DisplayName is required.' }
    $seen[$account] = $true
    [pscustomobject]@{
        Account = $account
        DisplayName = $user.DisplayName
        UserPrincipalName = "$account@lab.example"
        Department = $user.Department
        TargetOU = "OU=$($user.Department),OU=Lab,DC=lab,DC=example"
        Action = 'Review only - no account created'
    }
}

scripts/disk-space.ps1

Download this file ↓
# Sample: read-only local disk inventory. No remote hosts or credentials.

[CmdletBinding()]
param([ValidateRange(1,99)][int]$WarningPercent = 15)
$ErrorActionPreference = 'Stop'
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |
    Where-Object { $_.Size -gt 0 } |
    ForEach-Object {
        $freePercent = [math]::Round(100 * $_.FreeSpace / $_.Size, 1)
        [pscustomobject]@{
            Drive = $_.DeviceID
            FreeGB = [math]::Round($_.FreeSpace / 1GB, 1)
            FreePercent = $freePercent
            Status = if ($freePercent -lt $WarningPercent) { 'Review' } else { 'OK' }
        }
    }

scripts/service-health.ps1

Download this file ↓
# Sample: query explicitly selected local services without changing them.

[CmdletBinding()]
param([string[]]$Name = @('EventLog', 'Dnscache'))
foreach ($serviceName in $Name) {
    try {
        $service = Get-Service -Name $serviceName -ErrorAction Stop
        [pscustomobject]@{ Service = $service.Name; Status = [string]$service.Status; Note = 'Observed state' }
    } catch {
        [pscustomobject]@{ Service = $serviceName; Status = 'Unknown'; Note = 'Unavailable or inaccessible' }
    }
}
# A stopped service is not automatically a fault: compare with its intended state.

scripts/connectivity.ps1

Download this file ↓
# Sample: one authorised destination, one TCP port. No scanning or stored secrets.

[CmdletBinding()]
param(
    [ValidateNotNullOrEmpty()][string]$ComputerName = 'localhost',
    [ValidateRange(1,65535)][int]$Port = 443
)
$result = Test-NetConnection -ComputerName $ComputerName -Port $Port -WarningAction SilentlyContinue
[pscustomobject]@{
    Target = $ComputerName
    Port = $Port
    TcpReachable = [bool]$result.TcpTestSucceeded
}
# TCP reachability does not verify TLS, authentication or application health.

data/sample-users.csv

Download this file ↓
Account,DisplayName,Department
alex.sample,Alex Sample,Staff
sam.demo,Sam Demo,Admin
taylor.lab,Taylor Lab,Students

scripts/licence-report.ps1

Download this file ↓
# Offline capacity report. No sign-in or tenant access; input is a sanitised CSV.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-licences.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
foreach ($sku in (Import-Csv -LiteralPath $InputPath)) {
    $enabled = 0; $consumed = 0
    if ([string]::IsNullOrWhiteSpace($sku.Sku)) { throw 'Sku is required.' }
    if ($seen.ContainsKey($sku.Sku)) { throw "Duplicate SKU: $($sku.Sku)" }
    $seen[$sku.Sku] = $true
    if (-not [int]::TryParse($sku.Enabled,[ref]$enabled) -or $enabled -lt 0) { throw 'Enabled must be a non-negative integer.' }
    if (-not [int]::TryParse($sku.Consumed,[ref]$consumed) -or $consumed -lt 0) { throw 'Consumed must be a non-negative integer.' }
    $available = $enabled - $consumed
    [pscustomobject]@{
        Sku = $sku.Sku; Enabled = $enabled; Consumed = $consumed
        Available = $available
        Status = if ($available -lt 0) { 'Overallocated - review' } elseif ($available -eq 0) { 'At capacity' } else { 'Available' }
    }
}

data/sample-licences.csv

Download this file ↓
Sku,Enabled,Consumed
DEMO_COLLAB,25,21
DEMO_MAIL,10,10
DEMO_REVIEW,5,6

05 / Result & evidence

The onboarding planner and licence report can be run entirely offline with included fixtures. Host-dependent monitoring tools remain examples until run against an authorised lab machine.

Sample accountDepartmentProposed UPN
alex.sampleStaffalex.sample@lab.example
sam.demoAdminsam.demo@lab.example
taylor.labStudentstaylor.lab@lab.example
Actual onboarding-plan.ps1 output. Review-only proposals; no accounts created.
06 / Validation & troubleshooting
  • Onboarding plans contain only sample UPNs and do not modify identity systems.
  • Duplicate input is rejected rather than silently accepted.
  • Reports return structured objects before any display formatting.
  • Network targets and ports are explicit; the script performs no subnet scan.
SymptomInvestigation
Command unavailableConfirm the required Windows module exists; offline CSV tools do not need those modules.
Connectivity result is falseInspect name resolution, route, policy and listener separately; one probe cannot diagnose all four.

Screenshots & capture guide

The sample onboarding output is generated by the actual offline script. It demonstrates planning, not live account creation.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • A dry-run plan is useful before any account-creation workflow.
  • Return objects and let the caller choose CSV, JSON or a table.
  • Validate inputs and test failures as deliberately as the success path.

HYPER-V / NETWORKING / SERVICE BASELINE

Windows Server Deployment & Baseline

Configuration walkthrough · infrastructure validation requiredDownload project pack ↓

01 / Objective

Create a repeatable Windows Server VM baseline before installing a workload, with explicit network, capacity and service checks.

An authorised Hyper-V host or equivalent isolated hypervisor, Windows Server media and sufficient host resources. Example starting allocation: 2 vCPUs, 4 GB memory and a 60 GB virtual disk; adjust to the guest requirements.

02 / Architecture

Hypervisor / Isolated switch / Server VM / Baseline reportHypervisorIsolated switchServer VMBaseline report
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Plan isolation and resources

    Record VM allocations, switch type and intended workload. Keep the first boot isolated until the baseline is understood.

  2. Install and update

    Install from authorised media, create the lab administrator securely and apply required updates before adding application roles.

  3. Configure the network

    Set the lab hostname, address, gateway and DNS according to the diagram. Avoid conflicting addresses and record the intended resolver.

  4. Record the baseline

    Run the read-only baseline commands and capacity/service scripts. Save redacted results with the VM's configuration record.

  5. Validate recovery

    Take a lab checkpoint for experiments and separately define how important data would be backed up. Recheck services after a restart.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

configs/server-baseline.txt

Download this file ↓
# Read-only checks on an authorised Windows Server lab VM.

Get-ComputerInfo | Select-Object WindowsProductName,WindowsVersion,OsLastBootUpTime
Get-NetIPConfiguration
Get-DnsClientServerAddress -AddressFamily IPv4
Get-WindowsFeature | Where-Object Installed
Get-Service -Name EventLog,Dnscache
# Run the supplied disk-space.ps1 locally for capacity checks.

# Capture service states, role inventory and network settings in your lab record.

# Do not publish raw output: it may include hostnames or network details.

scripts/disk-space.ps1

Download this file ↓
# Sample: read-only local disk inventory. No remote hosts or credentials.

[CmdletBinding()]
param([ValidateRange(1,99)][int]$WarningPercent = 15)
$ErrorActionPreference = 'Stop'
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |
    Where-Object { $_.Size -gt 0 } |
    ForEach-Object {
        $freePercent = [math]::Round(100 * $_.FreeSpace / $_.Size, 1)
        [pscustomobject]@{
            Drive = $_.DeviceID
            FreeGB = [math]::Round($_.FreeSpace / 1GB, 1)
            FreePercent = $freePercent
            Status = if ($freePercent -lt $WarningPercent) { 'Review' } else { 'OK' }
        }
    }

scripts/service-health.ps1

Download this file ↓
# Sample: query explicitly selected local services without changing them.

[CmdletBinding()]
param([string[]]$Name = @('EventLog', 'Dnscache'))
foreach ($serviceName in $Name) {
    try {
        $service = Get-Service -Name $serviceName -ErrorAction Stop
        [pscustomobject]@{ Service = $service.Name; Status = [string]$service.Status; Note = 'Observed state' }
    } catch {
        [pscustomobject]@{ Service = $serviceName; Status = 'Unknown'; Note = 'Unavailable or inaccessible' }
    }
}
# A stopped service is not automatically a fault: compare with its intended state.

05 / Result & evidence

Expected result: a documented, reachable lab VM with a recorded baseline. The project does not claim a deployed server or verified production backup.

CheckExpected observation
Lab acceptance criterionRecorded address and DNS settings match the lab design.
Lab acceptance criterionRequired baseline services are in their intended state.
Lab acceptance criterionDisk capacity is recorded before workload installation.
Lab acceptance criterionA restart does not invalidate the documented baseline.
Expected observations for a future infrastructure run; not captured device output.
06 / Validation & troubleshooting
  • Recorded address and DNS settings match the lab design.
  • Required baseline services are in their intended state.
  • Disk capacity is recorded before workload installation.
  • A restart does not invalidate the documented baseline.
SymptomInvestigation
No connectivityCheck virtual switch assignment before changing the guest firewall.
Name lookup failsCompare the resolver setting with the lab DNS design and test name resolution independently.

Screenshots & capture guide

Capture a redacted role inventory, network baseline and capacity report only after an actual VM deployment.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • Separate the operating-system baseline from workload-specific settings.
  • Use a consistent preflight checklist before investigating application faults.
  • Checkpoints are useful for disposable experiments but are not a backup strategy.

RECOVERY / INTEGRITY / EXCEPTIONS

Backup & Restore Verification

Tested sample logic · isolated infrastructure steps documentedDownload project pack ↓

01 / Objective

Verify restored file contents with deterministic fixtures and distinguish matching, changed and missing files.

Two supplied local sample folders and PowerShell. No backup agent, production share or deletion is required.

02 / Architecture

Source files / Restore folder / SHA-256 compare / Exception reportSource filesRestore folderSHA-256 compareException report
Sanitised reference view · logical dependencies, not employer infrastructure
03 / Configuration walkthrough
  1. Define the scope

    Use the included source and restored folders. One file matches, one differs and one is intentionally missing.

  2. Run the verifier

    Execute backup-verify.ps1. It reads both folders, compares SHA-256 hashes and emits one result per expected file.

  3. Review exceptions

    Treat Mismatch and Missing as separate recovery issues. The script deliberately leaves all files unchanged.

  4. Expand the test plan

    For a real application, also check permissions, extra files, application consistency and the recovery procedure; hashes alone are insufficient.

  5. Record recovery evidence

    Keep the source selection, restore target and comparison result together. Measure recovery time only during a real timed exercise.

04 / Scripts & source files

Select a file to read its full source. Downloads are optional.

scripts/backup-verify.ps1

Download this file ↓
# Compare SHA-256 hashes in two explicitly selected folders. Never changes files.

[CmdletBinding()]
param(
    [string]$Source = (Join-Path $PSScriptRoot '../data/restore-source'),
    [string]$Restored = (Join-Path $PSScriptRoot '../data/restore-result')
)
$ErrorActionPreference = 'Stop'
$sourceRoot = (Resolve-Path -LiteralPath $Source).Path
$restoreRoot = (Resolve-Path -LiteralPath $Restored).Path
foreach ($file in (Get-ChildItem -LiteralPath $sourceRoot -File -Recurse)) {
    $relative = $file.FullName.Substring($sourceRoot.Length).TrimStart([char[]]'\/')
    $target = Join-Path $restoreRoot $relative
    $status = 'Missing'
    if (Test-Path -LiteralPath $target -PathType Leaf) {
        $original = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
        $restoredHash = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash
        $status = if ($original -eq $restoredHash) { 'Match' } else { 'Mismatch' }
    }
    [pscustomobject]@{ File = $relative; Status = $status }
}
# This checks expected file contents only, not ACLs, extra files or application recovery.

data/restore-source/handbook.txt

Download this file ↓
Sample operational handbook. Version 1.

data/restore-result/handbook.txt

Download this file ↓
Sample operational handbook. Version 1.

05 / Result & evidence

The included fixtures produce three distinct content-integrity outcomes. This validates the comparison tool, not a complete backup or disaster-recovery service.

Sample fileComparison
handbook.txtMatch
missing.txtMissing
settings.txtMismatch
Actual SHA-256 comparison of included synthetic fixtures.
06 / Validation & troubleshooting
  • handbook.txt returns Match.
  • settings.txt returns Mismatch.
  • missing.txt returns Missing.
  • The verifier never copies, replaces or removes a file.
SymptomInvestigation
Missing pathConfirm both folders exist and the restored file uses the expected relative name.
Hash mismatchConfirm the versions being compared before replacing anything; this tool is intentionally read-only.

Screenshots & capture guide

The comparison shown below is generated from the included fictional files, with a deliberate mismatch and missing file.

No employer screenshots or simulated terminal images are used as proof of deployment.

07 / Lessons learned

  • A completed backup job is not evidence of a successful restore.
  • Keep the source and restore target distinct when reviewing a recovery.
  • File hashes verify contents; permissions and application readiness need separate checks.

POWERSHELL / REVIEWABLE SOURCE

Automation with a clear purpose.

Six small tools, complete source and explicit scope. The CSV and fixture examples run offline; host checks require your own authorised Windows lab.

POWERSHELL / SAMPLE PROJECT

User Onboarding Planner

Validated CSV → reviewable account plan. No identity changes.

# Build a reviewable onboarding plan from synthetic input. Does not create accounts.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-users.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
$departments = @('Admin','Staff','Students')
View full source
# Build a reviewable onboarding plan from synthetic input. Does not create accounts.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-users.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
$departments = @('Admin','Staff','Students')
foreach ($user in (Import-Csv -LiteralPath $InputPath)) {
    $account = ([string]$user.Account).Trim().ToLowerInvariant()
    if ($account -notmatch '^[a-z][a-z0-9.-]{2,19}$') { throw "Invalid sample account: $account" }
    if ($seen.ContainsKey($account)) { throw "Duplicate account: $account" }
    if ($departments -notcontains $user.Department) { throw "Unknown department: $($user.Department)" }
    if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { throw 'DisplayName is required.' }
    $seen[$account] = $true
    [pscustomobject]@{
        Account = $account
        DisplayName = $user.DisplayName
        UserPrincipalName = "$account@lab.example"
        Department = $user.Department
        TargetOU = "OU=$($user.Department),OU=Lab,DC=lab,DC=example"
        Action = 'Review only - no account created'
    }
}

POWERSHELL / SAMPLE PROJECT

Microsoft 365 Licence Reporting

Offline entitlement report with capacity and overallocation states.

# Offline capacity report. No sign-in or tenant access; input is a sanitised CSV.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-licences.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
foreach ($sku in (Import-Csv -LiteralPath $InputPath)) {
View full source
# Offline capacity report. No sign-in or tenant access; input is a sanitised CSV.

[CmdletBinding()]
param([string]$InputPath = (Join-Path $PSScriptRoot '../data/sample-licences.csv'))
$ErrorActionPreference = 'Stop'
$seen = @{}
foreach ($sku in (Import-Csv -LiteralPath $InputPath)) {
    $enabled = 0; $consumed = 0
    if ([string]::IsNullOrWhiteSpace($sku.Sku)) { throw 'Sku is required.' }
    if ($seen.ContainsKey($sku.Sku)) { throw "Duplicate SKU: $($sku.Sku)" }
    $seen[$sku.Sku] = $true
    if (-not [int]::TryParse($sku.Enabled,[ref]$enabled) -or $enabled -lt 0) { throw 'Enabled must be a non-negative integer.' }
    if (-not [int]::TryParse($sku.Consumed,[ref]$consumed) -or $consumed -lt 0) { throw 'Consumed must be a non-negative integer.' }
    $available = $enabled - $consumed
    [pscustomobject]@{
        Sku = $sku.Sku; Enabled = $enabled; Consumed = $consumed
        Available = $available
        Status = if ($available -lt 0) { 'Overallocated - review' } elseif ($available -eq 0) { 'At capacity' } else { 'Available' }
    }
}

POWERSHELL / SAMPLE PROJECT

Disk Space Monitoring

Local capacity inventory with a configurable warning threshold.

# Sample: read-only local disk inventory. No remote hosts or credentials.

[CmdletBinding()]
param([ValidateRange(1,99)][int]$WarningPercent = 15)
$ErrorActionPreference = 'Stop'
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |
    Where-Object { $_.Size -gt 0 } |
View full source
# Sample: read-only local disk inventory. No remote hosts or credentials.

[CmdletBinding()]
param([ValidateRange(1,99)][int]$WarningPercent = 15)
$ErrorActionPreference = 'Stop'
Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' |
    Where-Object { $_.Size -gt 0 } |
    ForEach-Object {
        $freePercent = [math]::Round(100 * $_.FreeSpace / $_.Size, 1)
        [pscustomobject]@{
            Drive = $_.DeviceID
            FreeGB = [math]::Round($_.FreeSpace / 1GB, 1)
            FreePercent = $freePercent
            Status = if ($freePercent -lt $WarningPercent) { 'Review' } else { 'OK' }
        }
    }

POWERSHELL / SAMPLE PROJECT

Service Health Reporting

Explicit local service checks with unavailable-state handling.

# Sample: query explicitly selected local services without changing them.

[CmdletBinding()]
param([string[]]$Name = @('EventLog', 'Dnscache'))
foreach ($serviceName in $Name) {
    try {
        $service = Get-Service -Name $serviceName -ErrorAction Stop
View full source
# Sample: query explicitly selected local services without changing them.

[CmdletBinding()]
param([string[]]$Name = @('EventLog', 'Dnscache'))
foreach ($serviceName in $Name) {
    try {
        $service = Get-Service -Name $serviceName -ErrorAction Stop
        [pscustomobject]@{ Service = $service.Name; Status = [string]$service.Status; Note = 'Observed state' }
    } catch {
        [pscustomobject]@{ Service = $serviceName; Status = 'Unknown'; Note = 'Unavailable or inaccessible' }
    }
}
# A stopped service is not automatically a fault: compare with its intended state.

POWERSHELL / SAMPLE PROJECT

Network Connectivity Checker

One authorised destination and TCP port, without subnet scanning.

# Sample: one authorised destination, one TCP port. No scanning or stored secrets.

[CmdletBinding()]
param(
    [ValidateNotNullOrEmpty()][string]$ComputerName = 'localhost',
    [ValidateRange(1,65535)][int]$Port = 443
)
View full source
# Sample: one authorised destination, one TCP port. No scanning or stored secrets.

[CmdletBinding()]
param(
    [ValidateNotNullOrEmpty()][string]$ComputerName = 'localhost',
    [ValidateRange(1,65535)][int]$Port = 443
)
$result = Test-NetConnection -ComputerName $ComputerName -Port $Port -WarningAction SilentlyContinue
[pscustomobject]@{
    Target = $ComputerName
    Port = $Port
    TcpReachable = [bool]$result.TcpTestSucceeded
}
# TCP reachability does not verify TLS, authentication or application health.

POWERSHELL / SAMPLE PROJECT

Restore Integrity Checker

Compare expected files and report Match, Mismatch or Missing.

# Compare SHA-256 hashes in two explicitly selected folders. Never changes files.

[CmdletBinding()]
param(
    [string]$Source = (Join-Path $PSScriptRoot '../data/restore-source'),
    [string]$Restored = (Join-Path $PSScriptRoot '../data/restore-result')
)
View full source
# Compare SHA-256 hashes in two explicitly selected folders. Never changes files.

[CmdletBinding()]
param(
    [string]$Source = (Join-Path $PSScriptRoot '../data/restore-source'),
    [string]$Restored = (Join-Path $PSScriptRoot '../data/restore-result')
)
$ErrorActionPreference = 'Stop'
$sourceRoot = (Resolve-Path -LiteralPath $Source).Path
$restoreRoot = (Resolve-Path -LiteralPath $Restored).Path
foreach ($file in (Get-ChildItem -LiteralPath $sourceRoot -File -Recurse)) {
    $relative = $file.FullName.Substring($sourceRoot.Length).TrimStart([char[]]'\/')
    $target = Join-Path $restoreRoot $relative
    $status = 'Missing'
    if (Test-Path -LiteralPath $target -PathType Leaf) {
        $original = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
        $restoredHash = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash
        $status = if ($original -eq $restoredHash) { 'Match' } else { 'Mismatch' }
    }
    [pscustomobject]@{ File = $relative; Status = $status }
}
# This checks expected file contents only, not ACLs, extra files or application recovery.