How to Create a Deployment Package (ZIP) from Batch Script
Creating a deployment package bundles your application's compiled output, configuration files, assets, and dependencies into a single compressed archive that can be transferred, versioned, and deployed as a unit. ZIP packages are the universal standard for deployment artifacts because they are supported across all platforms, preserve directory structure, reduce transfer sizes, and can be versioned alongside release tags.
In this guide, we will explore how to create deployment ZIP packages from a Batch Script using built-in Windows tools and PowerShell.
Method 1: Using PowerShell Compress-Archive
The most straightforward method on modern Windows (PowerShell 5.0+):
@echo off
setlocal
set "source=dist"
set "output=deploy\MyApp_v1.0.0.zip"
echo Creating deployment package...
echo Source: %source%
echo Output: %output%
:: Verify source exists
if not exist "%source%\" (
echo [ERROR] Source directory not found: %source%
pause
exit /b 1
)
:: Create output directory
if not exist "deploy" mkdir "deploy"
:: Delete old package if exists
if exist "%output%" del "%output%"
powershell -NoProfile -Command ^
"try { Compress-Archive -Path '%source%\*' -DestinationPath '%output%' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if %errorlevel%==0 (
echo [SUCCESS] Package created: %output%
for %%F in ("%output%") do echo Size: %%~zF bytes
) else (
echo [ERROR] Package creation failed.
)
pause
Method 2: Versioned Package with Timestamp
Include the version number and build timestamp in the filename:
@echo off
setlocal enabledelayedexpansion
set "app_name=MyWebApp"
set "source=publish"
set "deploy_dir=deploy"
:: Verify source exists
if not exist "%source%\" (
echo [ERROR] Source directory not found: %source%
pause
exit /b 1
)
:: Read version
set "version=0.0.0"
if exist VERSION (
set /p "version=" < VERSION
)
:: Timestamp
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "timestamp=%dt:~0,4%%dt:~4,2%%dt:~6,2%_%dt:~8,2%%dt:~10,2%"
set "package=%deploy_dir%\%app_name%_v!version!_%timestamp%.zip"
if not exist "%deploy_dir%" mkdir "%deploy_dir%"
echo =============================================
echo DEPLOYMENT PACKAGE
echo =============================================
echo App: %app_name%
echo Version: !version!
echo Source: %source%
echo Package: !package!
echo.
echo Packaging...
if exist "!package!" del "!package!"
powershell -NoProfile -Command ^
"try { Compress-Archive -Path '%source%\*' -DestinationPath '!package!' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if %errorlevel%==0 (
for %%F in ("!package!") do set "size=%%~zF"
echo [SUCCESS] Package created.
echo File: !package!
echo Size: !size! bytes
) else (
echo [ERROR] Failed.
)
pause
Method 3: Selective Packaging (Include/Exclude)
Package only specific files and exclude development artifacts:
@echo off
setlocal
set "app_name=ProductionApp"
set "source=build\output"
set "staging=deploy\staging"
set "output=deploy\%app_name%.zip"
:: Verify source exists
if not exist "%source%\" (
echo [ERROR] Source directory not found: %source%
pause
exit /b 1
)
echo Preparing deployment package...
:: Clean staging area
if exist "%staging%" rmdir /s /q "%staging%"
mkdir "%staging%"
:: Copy production files only
echo Copying production files...
xcopy "%source%\*.exe" "%staging%\" /y /q >nul 2>nul
xcopy "%source%\*.dll" "%staging%\" /y /q >nul 2>nul
xcopy "%source%\*.config" "%staging%\" /y /q >nul 2>nul
xcopy "%source%\wwwroot\*" "%staging%\wwwroot\" /s /y /q >nul 2>nul
:: Verify staging is not empty
dir /b /a-d "%staging%" >nul 2>nul
if %errorlevel% neq 0 (
dir /b /ad "%staging%" >nul 2>nul
if %errorlevel% neq 0 (
echo [ERROR] No files matched for packaging.
rmdir /s /q "%staging%"
pause
exit /b 1
)
)
:: Exclude development files
echo Removing development artifacts...
del "%staging%\*.pdb" 2>nul
del "%staging%\appsettings.Development.json" 2>nul
:: Create ZIP
echo Creating package...
if exist "%output%" del "%output%"
powershell -NoProfile -Command ^
"try { Compress-Archive -Path '%staging%\*' -DestinationPath '%output%' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
set "zip_result=%errorlevel%"
:: Clean staging
rmdir /s /q "%staging%"
if %zip_result%==0 (
echo [SUCCESS] %output%
for %%F in ("%output%") do echo Size: %%~zF bytes
) else (
echo [ERROR] Failed.
)
pause
Method 4: Complete Build-and-Package Pipeline
@echo off
setlocal enabledelayedexpansion
set "app_name=WebAPI"
set "config=Release"
set "deploy_dir=artifacts"
:: Read version
set "version=1.0.0"
if exist VERSION (
set /p "version=" < VERSION
)
:: Build number
set "build=0"
if exist BUILD_NUMBER (
set /p "build=" < BUILD_NUMBER
)
set /a build+=1
>BUILD_NUMBER echo !build!
set "full_version=!version!.!build!"
set "package=%deploy_dir%\%app_name%_v!full_version!.zip"
echo =============================================
echo BUILD AND PACKAGE
echo %app_name% v!full_version!
echo =============================================
echo.
:: Step 1: Clean
echo [1/4] Cleaning...
if exist publish rmdir /s /q publish
if not exist "%deploy_dir%" mkdir "%deploy_dir%"
:: Step 2: Build
echo [2/4] Building...
dotnet publish -c %config% -o publish --no-restore > build.log 2>&1
if %errorlevel% neq 0 (
echo [FAIL] Build failed. See build.log
exit /b 1
)
:: Step 3: Add metadata
echo [3/4] Adding metadata...
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "build_date=%dt:~0,4%-%dt:~4,2%-%dt:~6,2% %dt:~8,2%:%dt:~10,2%:%dt:~12,2%"
(
echo version=!full_version!
echo built=!build_date!
echo machine=%COMPUTERNAME%
echo config=%config%
) > publish\build_info.txt
:: Step 4: Package
echo [4/4] Packaging...
if exist "!package!" del "!package!"
powershell -NoProfile -Command ^
"try { Compress-Archive -Path 'publish\*' -DestinationPath '!package!' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host ('[ERROR] ' + $_.Exception.Message); exit 1 }"
if %errorlevel%==0 (
for %%F in ("!package!") do set "size=%%~zF"
echo.
echo =============================================
echo PACKAGE READY
echo File: !package!
echo Size: !size! bytes
echo Version: !full_version!
echo =============================================
) else (
echo [ERROR] Packaging failed.
exit /b 1
)
pause
Method 5: Multi-Component Package
For applications with multiple deployable components (web app, API, database scripts):
@echo off
setlocal enabledelayedexpansion
set "version=2.1.0"
set "deploy_dir=releases\v%version%"
set "failed=0"
echo =============================================
echo MULTI-COMPONENT PACKAGE v%version%
echo =============================================
echo.
if not exist "%deploy_dir%" mkdir "%deploy_dir%"
:: Component 1: Web Frontend
echo [1/3] Packaging Web Frontend...
if not exist "frontend\dist\" (
echo [SKIP] frontend\dist not found.
set /a failed+=1
) else (
powershell -NoProfile -Command ^
"try { Compress-Archive -Path 'frontend\dist\*' -DestinationPath '%deploy_dir%\frontend_v%version%.zip' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host (' [FAIL] ' + $_.Exception.Message); exit 1 }"
if !errorlevel!==0 ( echo [OK] ) else ( set /a failed+=1 )
)
:: Component 2: API Backend
echo [2/3] Packaging API Backend...
if not exist "backend\publish\" (
echo [SKIP] backend\publish not found.
set /a failed+=1
) else (
powershell -NoProfile -Command ^
"try { Compress-Archive -Path 'backend\publish\*' -DestinationPath '%deploy_dir%\api_v%version%.zip' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host (' [FAIL] ' + $_.Exception.Message); exit 1 }"
if !errorlevel!==0 ( echo [OK] ) else ( set /a failed+=1 )
)
:: Component 3: Database Migrations
echo [3/3] Packaging Database Scripts...
if not exist "database\migrations\" (
echo [SKIP] database\migrations not found.
set /a failed+=1
) else (
powershell -NoProfile -Command ^
"try { Compress-Archive -Path 'database\migrations\*' -DestinationPath '%deploy_dir%\db_migrations_v%version%.zip' -Force -ErrorAction Stop; exit 0 }" ^
"catch { Write-Host (' [FAIL] ' + $_.Exception.Message); exit 1 }"
if !errorlevel!==0 ( echo [OK] ) else ( set /a failed+=1 )
)
:: Create manifest
echo.
echo Creating release manifest...
for /f "tokens=2 delims==" %%T in ('wmic os get LocalDateTime /value') do set "dt=%%T"
set "build_date=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%"
(
echo Release: v%version%
echo Date: !build_date!
echo Components:
) > "%deploy_dir%\MANIFEST.txt"
for %%F in ("%deploy_dir%\*.zip") do (
echo - %%~nxF>> "%deploy_dir%\MANIFEST.txt"
)
echo.
echo =============================================
if !failed!==0 (
echo RELEASE v%version% PACKAGED SUCCESSFULLY
) else (
echo RELEASE v%version% PACKAGED WITH !failed! FAILURE(S^)
)
echo Location: %deploy_dir%
echo =============================================
echo.
dir /b "%deploy_dir%"
pause
Verifying a Package
@echo off
setlocal
set "package=deploy\MyApp_v1.0.0.zip"
if not exist "%package%" (
echo [ERROR] Package not found: %package%
pause
exit /b 1
)
echo Contents of %package%:
echo =======================
echo.
powershell -NoProfile -Command ^
"Add-Type -AssemblyName System.IO.Compression.FileSystem;" ^
"$zip = [System.IO.Compression.ZipFile]::OpenRead('%package%');" ^
"try {" ^
" $zip.Entries | Format-Table FullName, Length, CompressedLength -AutoSize;" ^
" $totalSize = ($zip.Entries | Measure-Object Length -Sum).Sum;" ^
" $compSize = ($zip.Entries | Measure-Object CompressedLength -Sum).Sum;" ^
" $ratio = if ($totalSize -gt 0) { 1 - $compSize/$totalSize } else { 0 };" ^
" Write-Host ('Files: {0} Original: {1:N0} KB Compressed: {2:N0} KB Ratio: {3:P0}' -f" ^
" $zip.Entries.Count, ($totalSize/1KB), ($compSize/1KB), $ratio)" ^
"} finally { $zip.Dispose() }"
pause
Common Mistakes
The Wrong Way: ZIP Without Preserving Directory Structure
:: WRONG - Flattens all files into the root of the ZIP
powershell -Command "Compress-Archive -Path 'dist\*.*' -DestinationPath 'app.zip'"
Output Concern:
Using *.* with -Path may not include subdirectories or files without extensions. Use dist\* (without the dot) to include everything, and the directory structure is preserved inside the ZIP.
The Wrong Way: Not Excluding Development Files
:: WRONG - Packages debug symbols, test data, and dev configs
powershell -Command "Compress-Archive -Path 'bin\*' -DestinationPath 'deploy.zip'"
Debug symbols (.pdb), test projects, development configuration files, and source maps should be excluded from production packages. Use a staging directory to curate exactly what gets packaged.
Best Practices
- Include version in filename: Makes packages self-identifying (e.g.,
MyApp_v2.1.0_20240115.zip). - Exclude development artifacts: Remove
.pdb, test files, dev configs, andnode_modules. - Add build metadata: Include a
build_info.txtwith version, date, and build machine. - Verify after creation: List the ZIP contents to confirm the right files are packaged.
- Use a staging directory: Copy only production files to a clean directory before zipping.
Conclusion
Creating deployment packages from a Batch Script is handled by PowerShell's Compress-Archive cmdlet, which produces standard ZIP files from any directory structure. By combining version-stamped filenames, selective file inclusion, build metadata, and multi-component packaging, teams create professional deployment artifacts that are self-identifying, auditable, and ready for transfer to any deployment target. The key is curating what goes into the package through a staging directory pattern that ensures only production-ready files make it into the final archive.