A practical guide to deploying Windows without SCCM’s cost and complexity

  • Architecture Overview
  • Setting Up DHCP for PXE Boot
  • Installing iVentoy Server
  • Managing Windows ISOs
  • Creating Unattend Files
  • Building a Local WinGet Cache
  • Automated Software Installation

Why Choose This Solution?

If you’ve ever looked at SCCM pricing and thought “there must be a simpler way,” you’re not alone. This guide shows you how to build a professional Windows deployment system using free, open-source tools that gets you 90% of SCCM’s deployment capabilities at less than 10% of the cost. (for enterprise use)

Perfect for:

  • Schools deploying computer labs
  • PC repair shops needing quick, standardized installs
  • Small businesses preparing devices for Autopilot
  • IT departments without enterprise budgets

Architecture Overview

Here’s what we’re building:

How it works:

  1. PC boots from network and asks DHCP “where do I boot from?”
  2. DHCP points to iVentoy server
  3. iVentoy presents a menu of Windows versions
  4. Windows installs automatically using your unattend.xml
  5. Post-installation scripts grab software from local winget cache
  6. Done! Fully configured PC in 20-25 minutes

Step 1: Configure DHCP for PXE Boot

Your DHCP server needs to tell computers where to find the boot server. This is done using DHCP Options 66 and 67.

If you’re using MikroTik (like I do), here are the two commands you need:

# Tell PCs where the boot server is (Option 66)
/ip dhcp-server option add code=66 name=PXE-Server value="'192.168.1.100'"

# Tell PCs what boot file to use (Option 67)
/ip dhcp-server option add code=67 name=PXE-Bootfile value="'iventoy_loader_16000_bios.efi'"

# Apply these options to your DHCP network
/ip dhcp-server network set [find address=192.168.1.0/24] dhcp-option=PXE-Server,PXE-Bootfile

Replace 192.168.1.100 with your server’s IP address!

if you are running DHCP server on Windows machine

Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -OptionId 66 -Value "192.168.1.100"
Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -OptionId 67 -Value "iventoy_loader_16000_bios.efi"

Step 2: Install iVentoy

iVentoy is the magic that makes this easy. Unlike traditional PXE servers that require extracting ISOs, iVentoy serves them directly.

Installation ? just visit iVentoy (https://www.iventoy.com/en/index.html) website and just grab your Windows /Linux fresh release and start.

on Windows you should enable some firewall ports:

New-NetFirewallRule -DisplayName "iVentoy HTTP" -Direction Inbound -Protocol TCP -LocalPort 16000 -Action Allow

New-NetFirewallRule -DisplayName "iVentoy TFTP" -Direction Inbound -Protocol UDP -LocalPort 69 -Action Allow

New-NetFirewallRule -DisplayName "iVentoy Management" -Direction Inbound -Protocol TCP -LocalPort 26000 -Action Allow

Step 3: Manage Your Windows ISOs

How to Add ISOs? This is where iVentoy shines. Just drop your ISOs into a ‘ISO’ folder. No extraction needed!

Step 4: Create & apply Unattended Installation Files

Unattend.xml files automate Windows installation. No more clicking through setup screens!

Easy Way: Use the Online Generator

I recommend using Schneegans Unattend Generator:

🔗 https://schneegans.de/windows/unattend-generator/

This tool creates unattend.xml files through a simple web interface:

  1. Choose your Windows version
  2. Set timezone and language
  3. Configure privacy settings (disable telemetry, Cortana, etc.)
  4. Create local administrator account
  5. Add commands to run after installation
  6. Download your autounattend.xml and save it to your ” iVentoy script ” folder

Configure iVentoy to Use It

  1. Open iVentoy web interface: http://your-server:26000
  2. Go to Image Management
  3. Click on Windows ISO which will be used for unattended installation
  4. Add Auto Install Script and give full path of your unattended.xml (c:/Iventoy/user/scripts/unattended.xml)
  5. Associate it with your Windows ISO
  6. Select as default boot file
  7. *optional – protect with password

Note: We’ll cover advanced unattend.xml customization (registry edits, post-installation tasks, complex configurations) in the next article!

Step 5: Set Up Local WinGet with rewinged

Rewinged (https://github.com/jantari/rewinged) is a self-hosted WinGet repository. Think of it as your own private app store that works offline.

Why Use Rewinged?

  • WinGet ( Microsoft Package Manger ) is part of Windows 11 , can be easily configured with source as secure source of packages.
  • 6-8x faster software installation (local network vs internet)
  • Works offline – no internet needed
  • Control versions – deploy exactly what you want
  • Save bandwidth – download packages once, deploy many times

Install with Docker, feel free use my docker-compose.yml as a reference, using port 8443 & custom certificates in certs subfolder


services:
  rewinged:
    image: ghcr.io/jantari/rewinged:stable
    container_name: rewinged
    ports:
      - "8443:8080"
    volumes:
      - ./packages:/packages:ro
      - ./installers:/app/installers
      - ./certs:/certs:ro
    environment:
      - REWINGED_LISTEN=0.0.0.0:8080
      - REWINGED_MANIFESTPATH=/packages
      - REWINGED_AUTOINTERNALIZE=true
      - REWINGED_AUTOINTERNALIZEPATH=/app/installers
      - REWINGED_LOGLEVEL=debug
      - REWINGED_HTTPS=true
      - REWINGED_HTTPSCERTIFICATEFILE=/certs/cert.pem
      - REWINGED_HTTPSPRIVATEKEYFILE=/certs/private.key
    restart: unless-stopped

How to start?

Install Docker ( I’m using Rancher Desktop because of Docker desktop license terms of use, WSL is prerequirement ), upload certificate – or create own for Lab (Generating self-signed certificates on Windows | by Rory Braybrook | The new control plane | Medium) & start rewinged

sudo docker-compose up -d

Access it at: https://localhost:8443/api/information , output should be :

Populate with some test Software

Run this PowerShell script on any Windows PC with internet from folder where this rewinged docker is started:

# Downloads manifest YAML files from the official winget repository

$OutputDir = ".\manifests"
$PackagesToDownload = @(
    "Microsoft.Edge",
    "Google.Chrome",
    "Mozilla.Firefox",
    "7zip.7zip",
    "Adobe.Acrobat.Reader.64-bit",
    "VideoLAN.VLC",
    "Notepad++.Notepad++",
    "Git.Git",
    "Microsoft.VisualStudioCode",
    "Python.Python.3.12"
)

# Create output directory
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
foreach ($PackageId in $PackagesToDownload) {
    Write-Host "`nProcessing: $PackageId" -ForegroundColor Yellow
    
    # Parse PackageId to get publisher and name
    $parts = $PackageId -split '\.'
    $publisher = $parts[0]
    $name = ($parts[1..($parts.Length-1)] -join '.')
    
    # Get package info to find version
    $info = winget show --id $PackageId --exact 2>&1 | Out-String
    
    if ($info -match 'Version:\s+(.+)') {
        $version = $matches[1].Trim()
        Write-Host "  Found version: $version" -ForegroundColor Gray
        
        # Construct GitHub raw URL
        $manifestUrl = "https://raw.githubusercontent.com/microsoft/winget-pkgs/master/manifests/$($publisher.Substring(0,1).ToLower())/$publisher/$name/$version/"
        
        # Create local directory structure
        $localPath = Join-Path $OutputDir "$publisher\$name\$version"
        New-Item -ItemType Directory -Force -Path $localPath | Out-Null
        
        # Download manifest files (installer, locale, version)
        $manifestFiles = @(
            "$PackageId.installer.yaml",
            "$PackageId.locale.en-US.yaml",
            "$PackageId.yaml"
        )
        
        foreach ($file in $manifestFiles) {
            try {
                $url = $manifestUrl + $file
                $destination = Join-Path $localPath $file
                Invoke-WebRequest -Uri $url -OutFile $destination -ErrorAction Stop
                Write-Host "Downloaded: $file" -ForegroundColor Green
            }
            catch {
                Write-Host "Skipped: $file (not found or optional)" -ForegroundColor DarkGray
            }
        }
    }
    else {
        Write-Host "Could not determine version" -ForegroundColor Red
    }
}
Write-Host "Manifests downloaded to: $OutputDir" -ForegroundColor Green

Step 6: Automate Software Installation

Now let’s put it all together. This script runs after Windows installation and installs software from your local rewinged cache.

Create the Installation Script Install-Software.ps1

# save as Install-Software.ps1
# Automated software installation from local cache

$RewingedServer = "http://192.168.1.100:8443"
$LogFile = "C:\Windows\Logs\Software\WingetApps-Installation.log"

# Software to install
$Apps = @(
    @{Name="Microsoft Edge"; Id="Microsoft.Edge"},
    @{Name="Google Chrome"; Id="Google.Chrome"},
    @{Name="7-Zip"; Id="7zip.7zip"},
    @{Name="VLC Media Player"; Id="VideoLAN.VLC"}
)

# Configure WinGet to use local repository
winget source add --name "LocalWinget" --arg "$RewingedServer" --type "Microsoft.REST"

# Install each app
foreach ($App in $Apps) {
    Write-Host "Installing $($App.Name)..." -ForegroundColor Cyan
    
    winget install --id $App.Id --source "LocalWinget" --silent --accept-package-agreements
    
    if ($LASTEXITCODE -eq 0) {
        Write-Host "$($App.Name) installed" -ForegroundColor Green
    }
}

Reference It from Unattend.xml

When creating your unattend.xml, add this to the FirstLogonCommands section:

<FirstLogonCommands>
    <SynchronousCommand wcm:action="add">
        <Order>1</Order>
        <CommandLine>powershell.exe -ExecutionPolicy Bypass -File \\server\scripts\Install-Software.ps1</CommandLine>
        <Description>Install software from local repository</Description>
    </SynchronousCommand>
</FirstLogonCommands>

Have questions or want to share your deployment experience? Drop a comment below! I’d love to hear how you’re using this solution in your environment.

Found this helpful? Share it with your IT colleagues who might be looking for a cost-effective alternative to SCCM!


Leave a Reply

Your email address will not be published. Required fields are marked *