How to Migrate .bat to .ps1: Complete Windows Admin Guide

Master the migration from batch files to PowerShell scripts with step-by-step instructions, command translations, and best practices for Windows admins.

How to Migrate .bat files to .ps1: Complete Guide for Windows System Administrators

Table of Contents

1. [Introduction](#introduction) 2. [Understanding the Differences](#understanding-the-differences) 3. [Prerequisites for Migration](#prerequisites-for-migration) 4. [Step-by-Step Migration Process](#step-by-step-migration-process) 5. [Common Commands Translation](#common-commands-translation) 6. [Advanced Migration Techniques](#advanced-migration-techniques) 7. [Error Handling and Best Practices](#error-handling-and-best-practices) 8. [Testing and Validation](#testing-and-validation) 9. [Performance Optimization](#performance-optimization) 10. [Troubleshooting Common Issues](#troubleshooting-common-issues) 11. [Security Considerations](#security-considerations) 12. [Conclusion](#conclusion)

Introduction

In today's rapidly evolving IT landscape, system administrators and developers are increasingly recognizing the limitations of traditional batch files (.bat) and the superior capabilities of PowerShell scripts (.ps1). This comprehensive guide will walk you through the complete process of migrating your existing batch files to PowerShell scripts, ensuring improved functionality, better error handling, and enhanced security.

PowerShell has become the de facto standard for Windows automation and system administration tasks. Unlike batch files, which are limited by the constraints of the Command Prompt environment, PowerShell offers object-oriented programming capabilities, advanced error handling, and seamless integration with .NET Framework components.

The migration from .bat to .ps1 files is not just about translating commands; it's about leveraging PowerShell's powerful features to create more robust, maintainable, and efficient automation scripts. This article will provide you with practical examples, best practices, and step-by-step instructions to ensure a successful migration.

Understanding the Differences

Batch Files (.bat) Characteristics

Batch files are simple text files containing a series of commands that are executed sequentially by the Windows Command Processor (cmd.exe). They have been a staple of Windows automation for decades but come with significant limitations:

Limitations of Batch Files: - Limited error handling capabilities - Text-based processing only - Minimal programming constructs - Poor debugging support - Limited variable manipulation - No object-oriented features - Restricted access to system APIs

PowerShell Scripts (.ps1) Advantages

PowerShell scripts offer a modern approach to Windows automation with numerous advantages:

Benefits of PowerShell: - Object-oriented programming model - Rich error handling with try-catch blocks - Extensive cmdlet library - Integration with .NET Framework - Advanced debugging capabilities - Pipeline processing - Remote execution capabilities - Comprehensive help system - Strong typing support - Module system for code reusability

Key Architectural Differences

The fundamental difference between batch files and PowerShell scripts lies in their approach to data handling. Batch files work with text strings, while PowerShell works with .NET objects. This distinction is crucial when planning your migration strategy.

`batch REM Batch file example - text-based dir C:\ > output.txt findstr "Program Files" output.txt `

`powershell

PowerShell equivalent - object-based

Get-ChildItem -Path C:\ | Where-Object {$_.Name -like "Program Files"} `

Prerequisites for Migration

System Requirements

Before beginning the migration process, ensure your environment meets the following requirements:

1. PowerShell Version: Windows PowerShell 5.1 or PowerShell 7.x 2. Execution Policy: Configured to allow script execution 3. Administrative Rights: May be required for certain operations 4. Development Environment: PowerShell ISE or Visual Studio Code with PowerShell extension

Setting Up Your Environment

Configure your PowerShell environment for optimal development:

`powershell

Check PowerShell version

$PSVersionTable.PSVersion

Set execution policy (run as administrator)

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

Install PowerShell extension for VS Code

code --install-extension ms-vscode.PowerShell `

Documentation Preparation

Before starting the migration, document your existing batch files:

1. Inventory Creation: List all .bat files and their purposes 2. Dependency Mapping: Identify interdependencies between scripts 3. Input/Output Analysis: Document expected inputs and outputs 4. Error Scenarios: Catalog known error conditions and handling

Step-by-Step Migration Process

Phase 1: Analysis and Planning

#### Analyzing Existing Batch Files

Start by thoroughly analyzing your existing batch files to understand their functionality:

`batch @echo off REM Example batch file for analysis set SOURCE_DIR=C:\Source set DEST_DIR=C:\Destination set LOG_FILE=C:\Logs\backup.log

echo Starting backup process... >> %LOG_FILE% xcopy "%SOURCE_DIR%" "%DEST_DIR%" /E /Y /I if %ERRORLEVEL% EQU 0 ( echo Backup completed successfully >> %LOG_FILE% ) else ( echo Backup failed with error %ERRORLEVEL% >> %LOG_FILE% ) `

#### Creating Migration Plan

Develop a structured approach:

1. Priority Assessment: Rank scripts by importance and complexity 2. Resource Allocation: Estimate time and effort required 3. Testing Strategy: Plan comprehensive testing approach 4. Rollback Plan: Prepare contingency measures

Phase 2: Basic Translation

#### Simple Command Translation

Begin with straightforward command translations:

`batch REM Batch file commands echo Hello World pause cd C:\Windows dir `

`powershell

PowerShell equivalent

Write-Host "Hello World" Read-Host -Prompt "Press Enter to continue" Set-Location -Path "C:\Windows" Get-ChildItem `

#### Variable Handling Migration

Transform variable usage from batch to PowerShell:

`batch REM Batch variables set USERNAME=JohnDoe set COMPUTER_NAME=%COMPUTERNAME% echo User: %USERNAME% on Computer: %COMPUTER_NAME% `

`powershell

PowerShell variables

$Username = "JohnDoe" $ComputerName = $env:COMPUTERNAME Write-Host "User: $Username on Computer: $ComputerName" `

Phase 3: Advanced Feature Implementation

#### Conditional Logic Migration

Convert batch conditional statements to PowerShell:

`batch REM Batch conditional if exist "C:\temp\file.txt" ( echo File exists del "C:\temp\file.txt" ) else ( echo File not found ) `

`powershell

PowerShell conditional

if (Test-Path -Path "C:\temp\file.txt") { Write-Host "File exists" Remove-Item -Path "C:\temp\file.txt" } else { Write-Host "File not found" } `

#### Loop Structure Conversion

Transform batch loops to PowerShell equivalents:

`batch REM Batch for loop for %%i in (*.txt) do ( echo Processing %%i copy "%%i" "C:\Backup\" ) `

`powershell

PowerShell foreach loop

Get-ChildItem -Filter "*.txt" | ForEach-Object { Write-Host "Processing $($_.Name)" Copy-Item -Path $_.FullName -Destination "C:\Backup\" } `

Common Commands Translation

File and Directory Operations

#### Directory Navigation

`batch REM Batch directory commands cd /d C:\Windows pushd C:\Temp popd `

`powershell

PowerShell directory commands

Set-Location -Path "C:\Windows" Push-Location -Path "C:\Temp" Pop-Location `

#### File Operations

`batch REM Batch file operations copy source.txt destination.txt move oldname.txt newname.txt del unwanted.txt mkdir newfolder rmdir oldfolder `

`powershell

PowerShell file operations

Copy-Item -Path "source.txt" -Destination "destination.txt" Move-Item -Path "oldname.txt" -Destination "newname.txt" Remove-Item -Path "unwanted.txt" New-Item -ItemType Directory -Path "newfolder" Remove-Item -Path "oldfolder" -Recurse `

System Information Commands

`batch REM Batch system info echo %COMPUTERNAME% echo %USERNAME% echo %DATE% echo %TIME% `

`powershell

PowerShell system info

$env:COMPUTERNAME $env:USERNAME Get-Date -Format "MM/dd/yyyy" Get-Date -Format "HH:mm:ss" `

Network Commands

`batch REM Batch network commands ping google.com ipconfig /all netstat -an `

`powershell

PowerShell network commands

Test-Connection -ComputerName "google.com" Get-NetIPConfiguration Get-NetTCPConnection `

Process Management

`batch REM Batch process commands tasklist taskkill /PID 1234 start notepad.exe `

`powershell

PowerShell process commands

Get-Process Stop-Process -Id 1234 Start-Process -FilePath "notepad.exe" `

Advanced Migration Techniques

Function Creation

Transform repetitive batch code into PowerShell functions:

`batch REM Batch file with repetitive code :BACKUP_FILES xcopy "%1" "%2" /E /Y /I if %ERRORLEVEL% NEQ 0 goto ERROR goto :EOF

:ERROR echo Backup failed exit /b 1 `

`powershell

PowerShell function

function Backup-Files { param( [Parameter(Mandatory=$true)] [string]$Source, [Parameter(Mandatory=$true)] [string]$Destination ) try { Copy-Item -Path $Source -Destination $Destination -Recurse -Force Write-Host "Backup completed successfully" return $true } catch { Write-Error "Backup failed: $($_.Exception.Message)" return $false } }

Usage

$result = Backup-Files -Source "C:\Source" -Destination "C:\Backup" `

Parameter Handling

Implement robust parameter handling:

`batch REM Batch parameter handling if "%1"=="" goto USAGE set INPUT_FILE=%1 set OUTPUT_FILE=%2 if "%2"=="" set OUTPUT_FILE=default.txt goto PROCESS

:USAGE echo Usage: script.bat input_file [output_file] exit /b 1

:PROCESS echo Processing %INPUT_FILE% to %OUTPUT_FILE% `

`powershell

PowerShell parameter handling

param( [Parameter(Mandatory=$true, HelpMessage="Input file path")] [ValidateScript({Test-Path $_ -PathType Leaf})] [string]$InputFile, [Parameter(Mandatory=$false)] [string]$OutputFile = "default.txt" )

Write-Host "Processing $InputFile to $OutputFile" `

Error Handling Enhancement

Implement comprehensive error handling:

`batch REM Basic batch error handling copy source.txt dest.txt if %ERRORLEVEL% NEQ 0 ( echo Copy failed exit /b 1 ) `

`powershell

Advanced PowerShell error handling

try { Copy-Item -Path "source.txt" -Destination "dest.txt" -ErrorAction Stop Write-Host "Copy completed successfully" } catch [System.IO.FileNotFoundException] { Write-Error "Source file not found: $($_.Exception.Message)" exit 1 } catch [System.UnauthorizedAccessException] { Write-Error "Access denied: $($_.Exception.Message)" exit 2 } catch { Write-Error "Unexpected error: $($_.Exception.Message)" exit 99 } `

Logging Implementation

Create comprehensive logging systems:

`powershell

Advanced logging function

function Write-Log { param( [Parameter(Mandatory=$true)] [string]$Message, [Parameter(Mandatory=$false)] [ValidateSet("INFO", "WARN", "ERROR", "DEBUG")] [string]$Level = "INFO", [Parameter(Mandatory=$false)] [string]$LogPath = "C:\Logs\script.log" ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$Level] $Message" # Write to console with color coding switch ($Level) { "INFO" { Write-Host $logEntry -ForegroundColor Green } "WARN" { Write-Host $logEntry -ForegroundColor Yellow } "ERROR" { Write-Host $logEntry -ForegroundColor Red } "DEBUG" { Write-Host $logEntry -ForegroundColor Cyan } } # Write to log file Add-Content -Path $LogPath -Value $logEntry }

Usage

Write-Log -Message "Script started" -Level "INFO" Write-Log -Message "Processing file: $fileName" -Level "DEBUG" `

Error Handling and Best Practices

Comprehensive Error Management

Implement robust error handling strategies:

`powershell

Error handling best practices

function Process-Files { [CmdletBinding()] param( [string[]]$FilePaths ) $ErrorActionPreference = "Stop" $results = @() foreach ($filePath in $FilePaths) { try { # Validate file exists if (-not (Test-Path $filePath)) { throw "File not found: $filePath" } # Process file $content = Get-Content -Path $filePath $processedContent = $content | ForEach-Object { $_.ToUpper() } $results += [PSCustomObject]@{ FilePath = $filePath Status = "Success" LineCount = $content.Count } } catch { Write-Error "Failed to process $filePath`: $($_.Exception.Message)" $results += [PSCustomObject]@{ FilePath = $filePath Status = "Failed" Error = $_.Exception.Message } } } return $results } `

Input Validation

Implement thorough input validation:

`powershell function Validate-Parameters { param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ComputerName, [Parameter(Mandatory=$true)] [ValidateRange(1, 65535)] [int]$Port, [Parameter(Mandatory=$false)] [ValidateSet("TCP", "UDP")] [string]$Protocol = "TCP", [Parameter(Mandatory=$false)] [ValidateScript({ if (Test-Path $_ -PathType Container) { $true } else { throw "Directory does not exist: $_" } })] [string]$OutputDirectory = "C:\Temp" ) # Additional custom validation if ($ComputerName -notmatch '^[a-zA-Z0-9.-]+

How to Migrate .bat to .ps1: Complete Windows Admin Guide

) { throw "Invalid computer name format" } Write-Host "Parameters validated successfully" } `

Performance Optimization

Optimize your PowerShell scripts for better performance:

`powershell

Performance optimization techniques

function Optimize-FileProcessing { param( [string]$DirectoryPath ) # Use pipeline efficiently $results = Get-ChildItem -Path $DirectoryPath -Recurse -File | Where-Object { $_.Extension -eq '.txt' } | ForEach-Object -Process { [PSCustomObject]@{ Name = $_.Name Size = $_.Length LastModified = $_.LastWriteTime } } -End { Write-Host "Processed $($Input.Count) files" } return $results }

Memory-efficient large file processing

function Process-LargeFile { param( [string]$FilePath ) $reader = [System.IO.StreamReader]::new($FilePath) try { while (($line = $reader.ReadLine()) -ne $null) { # Process line without loading entire file into memory if ($line -match "ERROR") { Write-Host "Found error: $line" } } } finally { $reader.Close() } } `

Testing and Validation

Unit Testing Framework

Implement comprehensive testing for your migrated scripts:

`powershell

Simple testing framework

function Test-Function { param( [string]$TestName, [scriptblock]$TestCode, [object]$ExpectedResult ) try { $actualResult = & $TestCode if ($actualResult -eq $ExpectedResult) { Write-Host "✓ PASS: $TestName" -ForegroundColor Green return $true } else { Write-Host "✗ FAIL: $TestName - Expected: $ExpectedResult, Actual: $actualResult" -ForegroundColor Red return $false } } catch { Write-Host "✗ ERROR: $TestName - $($_.Exception.Message)" -ForegroundColor Red return $false } }

Example test cases

function Run-Tests { $testResults = @() # Test file existence check $testResults += Test-Function -TestName "File Exists Check" -TestCode { Test-Path -Path "C:\Windows\System32\notepad.exe" } -ExpectedResult $true # Test string manipulation $testResults += Test-Function -TestName "String Upper Case" -TestCode { "hello".ToUpper() } -ExpectedResult "HELLO" # Test mathematical operation $testResults += Test-Function -TestName "Addition Test" -TestCode { 2 + 3 } -ExpectedResult 5 $passCount = ($testResults | Where-Object { $_ -eq $true }).Count $totalCount = $testResults.Count Write-Host "`nTest Summary: $passCount/$totalCount tests passed" -ForegroundColor Cyan }

Run-Tests `

Integration Testing

Test the complete workflow:

`powershell function Test-MigratedScript { param( [string]$ScriptPath, [hashtable]$TestParameters ) Write-Host "Testing migrated script: $ScriptPath" # Create test environment $testDir = "C:\Temp\ScriptTest_$(Get-Date -Format 'yyyyMMdd_HHmmss')" New-Item -ItemType Directory -Path $testDir -Force | Out-Null try { # Execute script with test parameters $result = & $ScriptPath @TestParameters # Validate results if ($result) { Write-Host "✓ Script executed successfully" -ForegroundColor Green } else { Write-Host "✗ Script execution failed" -ForegroundColor Red } # Additional validation logic here } catch { Write-Host "✗ Script execution error: $($_.Exception.Message)" -ForegroundColor Red } finally { # Cleanup test environment Remove-Item -Path $testDir -Recurse -Force -ErrorAction SilentlyContinue } } `

Performance Optimization

Memory Management

Optimize memory usage in your PowerShell scripts:

`powershell

Memory-efficient processing

function Process-LargeDataSet { param( [string[]]$DataSources ) foreach ($source in $DataSources) { # Process data in chunks to avoid memory issues $chunkSize = 1000 $totalProcessed = 0 do { $data = Get-Data -Source $source -Skip $totalProcessed -Take $chunkSize if ($data.Count -gt 0) { # Process chunk $data | ForEach-Object { # Process individual item Process-Item -Item $_ } $totalProcessed += $data.Count # Force garbage collection periodically if ($totalProcessed % 10000 -eq 0) { [System.GC]::Collect() } } } while ($data.Count -eq $chunkSize) Write-Host "Processed $totalProcessed items from $source" } } `

Parallel Processing

Leverage PowerShell's parallel processing capabilities:

`powershell

Parallel processing example

function Process-FilesParallel { param( [string[]]$FilePaths ) $FilePaths | ForEach-Object -Parallel { $filePath = $_ try { # Process file $content = Get-Content -Path $filePath $wordCount = ($content -split '\s+').Count [PSCustomObject]@{ FilePath = $filePath WordCount = $wordCount ProcessedBy = $env:COMPUTERNAME Timestamp = Get-Date } } catch { [PSCustomObject]@{ FilePath = $filePath Error = $_.Exception.Message ProcessedBy = $env:COMPUTERNAME Timestamp = Get-Date } } } -ThrottleLimit 5 } `

Troubleshooting Common Issues

Execution Policy Problems

Address common execution policy issues:

`powershell

Check current execution policy

Get-ExecutionPolicy -List

Set execution policy for current user

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Bypass execution policy for specific script

PowerShell.exe -ExecutionPolicy Bypass -File "C:\Scripts\MyScript.ps1" `

Path and Encoding Issues

Handle path and encoding problems:

`powershell

Handle paths with spaces and special characters

function Resolve-SafePath { param( [string]$Path ) # Resolve relative paths $resolvedPath = Resolve-Path -Path $Path -ErrorAction SilentlyContinue if ($resolvedPath) { return $resolvedPath.Path } else { # Handle non-existent paths return [System.IO.Path]::GetFullPath($Path) } }

Handle encoding issues

function Read-FileWithEncoding { param( [string]$FilePath ) # Detect encoding $encoding = Get-FileEncoding -Path $FilePath # Read file with correct encoding return Get-Content -Path $FilePath -Encoding $encoding }

function Get-FileEncoding { param( [string]$Path ) $bytes = Get-Content -Path $Path -AsByteStream -TotalCount 4 if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { return "UTF8" } elseif ($bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { return "Unicode" } elseif ($bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { return "BigEndianUnicode" } else { return "ASCII" } } `

Variable Scope Issues

Address variable scope problems:

`powershell

Global variable management

$Global:ConfigData = @{}

function Initialize-Configuration { $Global:ConfigData = @{ LogPath = "C:\Logs\application.log" MaxRetries = 3 TimeoutSeconds = 30 } }

function Get-ConfigValue { param( [string]$Key ) if ($Global:ConfigData.ContainsKey($Key)) { return $Global:ConfigData[$Key] } else { throw "Configuration key not found: $Key" } }

Script scope variables

function Process-WithScriptScope { $Script:ProcessedCount = 0 function Update-Counter { $Script:ProcessedCount++ } # Process items 1..10 | ForEach-Object { Update-Counter } Write-Host "Processed $Script:ProcessedCount items" } `

Security Considerations

Secure Credential Handling

Implement secure credential management:

`powershell

Secure credential storage and retrieval

function Store-SecureCredential { param( [string]$CredentialName, [string]$Username ) $credential = Get-Credential -UserName $Username -Message "Enter password for $Username" $securePassword = $credential.Password | ConvertFrom-SecureString $credentialData = @{ Username = $credential.UserName Password = $securePassword } $credentialData | ConvertTo-Json | Out-File -FilePath "C:\Secure\$CredentialName.json" -Encoding UTF8 }

function Get-SecureCredential { param( [string]$CredentialName ) $credentialPath = "C:\Secure\$CredentialName.json" if (Test-Path $credentialPath) { $credentialData = Get-Content -Path $credentialPath | ConvertFrom-Json $securePassword = $credentialData.Password | ConvertTo-SecureString return New-Object System.Management.Automation.PSCredential($credentialData.Username, $securePassword) } else { throw "Credential not found: $CredentialName" } } `

Input Sanitization

Implement robust input sanitization:

`powershell function Sanitize-Input { param( [string]$Input, [string]$Type = "General" ) switch ($Type) { "FileName" { # Remove invalid file name characters $invalidChars = [System.IO.Path]::GetInvalidFileNameChars() foreach ($char in $invalidChars) { $Input = $Input.Replace($char, '') } return $Input } "Path" { # Remove invalid path characters $invalidChars = [System.IO.Path]::GetInvalidPathChars() foreach ($char in $invalidChars) { $Input = $Input.Replace($char, '') } return $Input } "SQL" { # Basic SQL injection prevention $Input = $Input.Replace("'", "''") $Input = $Input.Replace(";", "") $Input = $Input.Replace("--", "") return $Input } default { # General sanitization $Input = $Input.Trim() $Input = $Input -replace '[<>"]', '' return $Input } } } `

Audit Logging

Implement comprehensive audit logging:

`powershell function Write-AuditLog { param( [string]$Action, [string]$Resource, [string]$User = $env:USERNAME, [string]$Result = "Success", [string]$Details = "" ) $auditEntry = [PSCustomObject]@{ Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" User = $User Computer = $env:COMPUTERNAME Action = $Action Resource = $Resource Result = $Result Details = $Details ProcessId = $PID } $logPath = "C:\Logs\Audit\audit_$(Get-Date -Format 'yyyyMM').log" # Ensure log directory exists $logDir = Split-Path $logPath -Parent if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } # Write audit entry $auditEntry | ConvertTo-Json -Compress | Add-Content -Path $logPath }

Usage example

Write-AuditLog -Action "FileAccess" -Resource "C:\Sensitive\data.txt" -Result "Success" `

Conclusion

Migrating from .bat files to .ps1 PowerShell scripts represents a significant step forward in Windows automation capabilities. This comprehensive guide has covered the essential aspects of migration, from basic command translation to advanced security considerations.

Key Takeaways

1. Planning is Critical: Thorough analysis and planning ensure successful migration 2. Gradual Approach: Migrate scripts incrementally, starting with simpler ones 3. Leverage PowerShell Features: Take advantage of object-oriented programming, error handling, and advanced features 4. Testing is Essential: Implement comprehensive testing strategies 5. Security First: Always consider security implications and implement appropriate measures 6. Performance Matters: Optimize scripts for better performance and resource usage

Best Practices Summary

- Use proper error handling with try-catch blocks - Implement comprehensive logging and audit trails - Validate all inputs and sanitize user data - Use PowerShell's built-in cmdlets instead of external executables when possible - Follow PowerShell naming conventions and coding standards - Document your scripts thoroughly - Implement proper credential management - Test scripts in isolated environments before production deployment

Future Considerations

As you complete your migration, consider these future enhancements:

- Module Development: Convert related functions into PowerShell modules - Desired State Configuration (DSC): Implement DSC for configuration management - PowerShell Classes: Utilize PowerShell classes for complex object modeling - Workflow Integration: Integrate with PowerShell workflows for long-running processes - Cross-Platform Compatibility: Consider PowerShell Core for cross-platform scenarios

The migration from batch files to PowerShell scripts is an investment in the future of your automation infrastructure. By following the guidelines and best practices outlined in this article, you'll create more robust, maintainable, and secure automation solutions that will serve your organization well into the future.

Remember that migration is not just about converting syntax; it's about embracing a more powerful and flexible approach to Windows automation. Take the time to learn PowerShell's advanced features and incorporate them into your migrated scripts to realize the full benefits of this powerful scripting platform.

Tags

  • Automation
  • Batch Files
  • PowerShell
  • Script Migration
  • Windows Administration

Related Articles

Popular Technical Articles & Tutorials

Explore our comprehensive collection of technical articles, programming tutorials, and IT guides written by industry experts:

Browse all 8+ technical articles | Read our IT blog

How to Migrate .bat to .ps1: Complete Windows Admin Guide