π Welcome to PowerShell Essentials
π― About This Interactive eBook
Author: Mahammad Haneef
Version: 2.0 (June 2026)
Total Chapters: 18 + Comprehensive Cheat Sheet
Estimated Learning Time: 60-80 hours
Code Examples: 500+ practical examples
Exercises: 150+ hands-on exercises with solutions
What You'll Learn
This comprehensive guide will take you from complete beginner to advanced PowerShell professional. Whether you're coming from DOS/CMD background or starting fresh, this book provides:
- Solid Foundation: Understanding PowerShell's architecture, philosophy, and object-oriented approach
- Practical Skills: Real-world automation scenarios, system administration, and DevOps solutions
- Hands-on Practice: 150+ exercises with detailed solutions and explanations
- Career Development: Skills that increase your market value by 30-50% in the IT industry
- Modern Techniques: Latest PowerShell 7+ features, modules, and best practices
- Production-Ready Code: Enterprise-grade scripts and automation frameworks
How to Use This eBook
π Navigation Tips
- Sidebar Navigation: Click any chapter in the left sidebar to jump directly to it
- Sequential Reading: Use "Next" and "Previous" buttons at the bottom of each chapter
- Keyboard Shortcuts: Use arrow keys (β β) for quick navigation between chapters
- Dark Mode: Toggle with the π button in the header for comfortable reading
- Font Adjustment: Use A+ and A- buttons to customize text size
- Code Copying: Click "Copy" buttons to instantly copy code examples to clipboard
- Interactive Exercises: Click "Show Answer" to reveal solutions after attempting exercises
- Search Function: Use Ctrl+F to search across all chapters
Book Structure
| Part | Chapters | Focus | Level | Duration |
|---|---|---|---|---|
| Part I: Foundations | 1-5 | Introduction, Setup, Essential Commands, DOS Translation, Help System | Beginner β | 10-12 hours |
| Part II: Core Skills | 6-10 | Variables, Operators, Control Flow, Functions, Error Handling | Intermediate ββ | 15-20 hours |
| Part III: Practical PowerShell | 11-18 | Files, Registry, Services, WMI, Remoting, Modules, Advanced Topics, Best Practices | Advanced βββ | 35-45 hours |
| Quick Reference | Cheat Sheet | Complete command reference, patterns, shortcuts | All Levels βββ | Always Available |
Detailed Chapter Breakdown
π Part I: Foundations (Chapters 1-5)
- Chapter 1: Why PowerShell? - Understanding the need and benefits
- Chapter 2: Getting Started - Installation, environment setup, first commands
- Chapter 3: Essential Commands - Core cmdlets and pipeline basics
- Chapter 4: DOS to PowerShell Translation - Perfect for CMD users
- Chapter 5: PowerShell Help System - Mastering Get-Help and documentation
π‘ Part II: Core Skills (Chapters 6-10)
- Chapter 6: Variables and Data Types - Strings, numbers, arrays, hashtables
- Chapter 7: Operators - Comparison, logical, arithmetic operations
- Chapter 8: Control Flow - If/else, switch, loops (for, foreach, while)
- Chapter 9: Functions and Scripts - Creating reusable code
- Chapter 10: Error Handling - Try/catch, error management strategies
π Part III: Practical PowerShell (Chapters 11-18)
- Chapter 11: Working with Files and Folders - File system automation
- Chapter 12: Registry Management - Reading, writing, backing up registry
- Chapter 13: Services and Processes - System management and monitoring
- Chapter 14: WMI and CIM - Windows Management Instrumentation
- Chapter 15: Remote Management - PowerShell remoting and remote execution
- Chapter 16: Modules and Script Management - Creating and managing modules
- Chapter 17: Advanced Topics - Classes, regex, jobs, performance optimization
- Chapter 18: Best Practices & Real-World Projects - Production-ready solutions
Prerequisites
This book is designed to be accessible to everyone:
- β Basic computer literacy - Comfortable using Windows
- β Windows operating system familiarity - Understanding of files, folders, applications
- β No prior programming experience required! - We start from absolute basics
- β Willingness to practice and experiment - Learning by doing is key
- β Optional: CMD/DOS experience helps but is not required
What Makes This Book Different?
π Unique Features
- π― DOS/CMD Background Support: Perfect for traditional Windows admins transitioning to PowerShell
- π» Interactive Learning: Copy and try every code example directly in your environment
- π Real-World Focus: Actual scenarios from UAE/Middle East IT industry and global enterprises
- πΌ Career-Oriented: Skills that employers actively seek in 2026 and beyond
- π Progressive Difficulty: Builds knowledge systematically from basics to advanced
- π Comprehensive Cheat Sheet: Quick reference for all commands and patterns
- π§ Production-Ready Code: Enterprise-grade scripts you can use immediately
- π 150+ Exercises: Hands-on practice with detailed solutions
- π 3 Complete Projects: Real-world automation projects in Chapter 18
- π¨ Modern Design: Dark mode, adjustable fonts, responsive layout
Learning Path Recommendations
π For Complete Beginners
- Week 1-2: Chapters 1-5 (Foundations) - Understand basics and setup
- Week 3-4: Chapters 6-10 (Core Skills) - Master programming fundamentals
- Week 5-8: Chapters 11-18 (Practical) - Apply skills to real scenarios
- Daily Practice: Complete all exercises before moving forward
- Weekly Review: Revisit previous chapters and practice
- Estimated Time: 8-10 weeks (1-2 hours daily)
β‘ For CMD/DOS Users (Fast Track)
- Day 1-2: Chapters 1-3 - Understand PowerShell philosophy and differences
- Day 3: Chapter 4 - DOS to PowerShell translation (your quick reference)
- Day 4-5: Chapters 6-8 - Variables, operators, control flow
- Week 2-3: Chapters 11-15 - Jump to practical system administration
- Week 4: Chapters 16-18 - Advanced topics and best practices
- Reference: Use Cheat Sheet frequently
- Estimated Time: 4-5 weeks
π For Quick Reference Users
- Start Here: PowerShell Cheat Sheet (comprehensive quick reference)
- Topic-Based: Use sidebar to find specific topics you need
- Code Examples: Review and copy examples from relevant chapters
- Best For: Experienced users looking for specific solutions
What You'll Build
By the end of this book, you'll have created:
- π₯οΈ Server Health Check System - Monitor multiple servers with HTML reports
- πΎ Automated Backup Solution - Complete backup system with retention policies
- π₯ User Management System - Bulk user creation and management from CSV
- π Custom PowerShell Modules - Reusable tools for your organization
- π§ System Administration Scripts - 50+ production-ready automation scripts
Career Impact
π° Market Value
PowerShell skills are in high demand across industries:
- System Administrators: Essential skill for Windows infrastructure
- DevOps Engineers: Automation and CI/CD pipeline integration
- Cloud Engineers: Azure and AWS management (PowerShell Core)
- Security Professionals: Security automation and compliance
- Salary Impact: 30-50% increase in market value with PowerShell expertise
Support & Community
π GitHub: https://github.com/haneefputtur/PowerShell-Essentials
π¬ Questions? Open an issue on GitHub for support
π€ Contribute: Found an error or want to improve content? Pull requests welcome!
β Star the Repo: If you find this book helpful, please star it on GitHub
Version History
- Version 2.0 (June 2026): Expanded to 18 chapters, added 3 real-world projects, comprehensive cheat sheet
- Version 1.0 (March 2026): Initial release with 15 chapters
Acknowledgments
Special thanks to:
- The PowerShell community for continuous innovation and support
- Microsoft documentation team for excellent reference materials
- IT professionals from UAE and Middle East who shared real-world scenarios
- Early readers and reviewers who provided valuable feedback
- Open-source contributors who make learning accessible to everyone
π Ready to Begin Your PowerShell Journey?
Start with Chapter 1 to understand why PowerShell is essential for modern IT professionals, or jump to the Cheat Sheet if you need quick reference.
Remember: The best way to learn PowerShell is by doing. Don't just readβpractice every example!
Chapter 1: Why PowerShell? Welcome to Modern Automation
Estimated Reading Time: 45 minutes
Difficulty Level: Beginner β
Prerequisites: None
π Learning Objectives
- Understand the history and evolution of Windows command-line tools
- Identify when and why to use PowerShell over other tools
- Recognize PowerShell's role in modern IT careers
- Execute your first PowerShell commands
- Appreciate the power of object-oriented scripting
1.1 The Evolution Story: From DOS to PowerShell
1.1.1 The Beginning - MS-DOS (1981-1995)
In 1981, Microsoft introduced MS-DOS (Microsoft Disk Operating System), a text-based operating system that required users to type commands to perform tasks. While revolutionary for its time, DOS had significant limitations:
- Text-based output only - Everything was plain text
- Limited scripting capabilities - Batch files were basic
- No built-in networking - Network commands were added later
- Single-tasking - Could only run one program at a time
dir - List directory contents
cd - Change directory
copy - Copy files
del - Delete files
type - Display file contents
1.1.2 The Windows Era - Command Prompt (1993-Present)
When Windows NT was released in 1993, Microsoft introduced cmd.exe (Command Prompt), which was essentially DOS with some Windows integration.
1.1.3 The PowerShell Revolution (2006-Present)
In November 2006, Microsoft released PowerShell 1.0, a game-changing automation platform built on the .NET Framework.
| Version | Year | Key Features |
|---|---|---|
| PowerShell 1.0 | 2006 | Initial release, object-based pipeline |
| PowerShell 2.0 | 2009 | Remoting, background jobs, modules |
| PowerShell 5.1 | 2017 | Last Windows PowerShell version |
| PowerShell 7.0 | 2020 | Unified cross-platform experience |
| PowerShell 7.5+ | 2026 | Current version with AI integration |
1.2 Why PowerShell Matters in 2026
π Industry Statistics (2026)
- 92% of Fortune 500 companies use PowerShell
- 78% of Windows Server administrators use PowerShell daily
- 65% of DevOps teams include PowerShell in their toolchain
- 50% increase in PowerShell-related job postings since 2020
Career Impact
| Role | Without PowerShell | With PowerShell | Increase |
|---|---|---|---|
| System Administrator | AED 8,000/month | AED 12,000/month | +50% |
| DevOps Engineer | AED 15,000/month | AED 20,000/month | +33% |
| Cloud Engineer | AED 18,000/month | AED 24,000/month | +33% |
1.3 Your First PowerShell Commands
Let's get hands-on! Here are some simple commands to try:
Example 1: Display Current Date and Time
Get-Date
Output:
Friday, June 5, 2026 2:38:15 PM
Example 2: Get System Information
Get-ComputerInfo | Select-Object CsName, WindowsVersion, OsArchitecture
Example 3: List Running Processes
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
1.4 Try It Yourself - Exercises
Exercise 1: Display Current Date
Task: Show the current date and time
Click to see solution
Get-Date
Exercise 2: Get Your Computer Name
Task: Display your computer's name
Click to see solution
# Method 1: Environment variable
$env:COMPUTERNAME
# Method 2: Using cmdlet
Get-ComputerInfo | Select-Object CsName
Exercise 3: List All Running Processes
Task: Display all currently running processes
Click to see solution
Get-Process
Chapter Summary
β Key Takeaways
- PowerShell evolved from DOS (1981) and CMD to become the modern automation standard
- 92% of Fortune 500 companies use PowerShell
- PowerShell skills can increase salary by 30-50%
- Object-based pipeline is PowerShell's key advantage
- PowerShell 7+ is cross-platform (Windows, Linux, macOS)
Chapter 2: PowerShell vs. Command Prompt
π Learning Objectives
By the end of this chapter, you will be able to:
- Understand the fundamental architectural differences between CMD and PowerShell
- Recognize when to use each tool appropriately
- Translate common CMD commands to PowerShell equivalents
- Appreciate PowerShell's advantages in modern IT environments
- Make informed decisions about tool selection for specific tasks
2.1 Introduction: Why This Comparison Matters
If you've been working with Windows for any length of time, you're probably familiar with the Command Prompt (cmd.exe). It's been the go-to tool for command-line operations since Windows NT. But with PowerShell now the default shell in Windows 10 and 11, many administrators wonder: Should I stick with CMD or switch to PowerShell?
The short answer: PowerShell is the future, but understanding both toolsβand knowing when to use eachβis crucial for any Windows professional.
Starting with Windows 10 build 14971 (2017), PowerShell replaced Command Prompt in the Win+X menu. This wasn't arbitraryβMicrosoft is actively pushing PowerShell as the primary management tool.
2.2 Architectural Differences: Under the Hood
2.2.1 Text vs. Objects: The Fundamental Difference
The most critical difference between CMD and PowerShell lies in how they handle data:
| Aspect | Command Prompt (CMD) | PowerShell |
|---|---|---|
| Data Type | Text strings | Objects with properties and methods |
| Pipeline | Passes text between commands | Passes objects between cmdlets |
| Output Parsing | Requires string manipulation | Direct property access |
| Framework | DOS-based | .NET Framework |
| Scripting | Batch files (.bat, .cmd) | Scripts (.ps1) with full programming features |
2.2.2 Real-World Example: Finding Large Files
Let's see the difference in action with a practical task: finding files larger than 100MB.
Command Prompt Approach:
REM This is complex and limited in CMD
forfiles /S /M * /C "cmd /c if @fsize GEQ 104857600 echo @path @fsize"
Issues:
- Complex syntax that's hard to remember
- Output is just textβdifficult to sort or filter further
- Limited formatting options
- No easy way to perform actions on results
PowerShell Approach:
# Clean, readable, and powerful
Get-ChildItem -Path C:\ -Recurse -File -ErrorAction SilentlyContinue |
Where-Object {$_.Length -gt 100MB} |
Sort-Object Length -Descending |
Select-Object Name, Directory, @{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}}
Advantages:
- β Readable and self-documenting
- β Easy to modify and extend
- β Results are objects you can manipulate further
- β Can easily export to CSV, JSON, or other formats
2.3 Command Syntax Comparison
2.3.1 Common Tasks Side-by-Side
| Task | Command Prompt | PowerShell |
|---|---|---|
| List files | dir |
Get-ChildItem (alias: dir, ls) |
| Change directory | cd folder |
Set-Location folder (alias: cd) |
| Copy files | copy source dest |
Copy-Item source dest (alias: copy, cp) |
| Delete files | del file |
Remove-Item file (alias: del, rm) |
| Display file content | type file.txt |
Get-Content file.txt (alias: type, cat) |
| Find text in files | findstr "text" *.txt |
Select-String "text" *.txt |
| List processes | tasklist |
Get-Process |
| Kill process | taskkill /PID 1234 |
Stop-Process -Id 1234 |
| Network info | ipconfig |
Get-NetIPAddress |
| System info | systeminfo |
Get-ComputerInfo |
PowerShell includes aliases for common CMD commands (like
dir, cd, copy) to ease the transition. However, learning the full cmdlet names (like Get-ChildItem) is recommended for scripts.
2.4 When to Use Each Tool
2.4.1 Use Command Prompt When:
β Appropriate CMD Use Cases:
- Legacy batch files: Existing .bat scripts that work fine
- Simple, one-off commands: Quick
ipconfigorping - Compatibility requirements: Working with very old systems
- Minimal overhead: When you need the absolute fastest startup time
- Third-party tools: Some legacy tools expect CMD environment
Example Scenario:
REM Quick network diagnostics
ipconfig /all
ping google.com
tracert 8.8.8.8
2.4.2 Use PowerShell When:
β PowerShell is Superior For:
- System administration: Managing users, services, registry
- Automation: Any task you'll repeat more than once
- Data manipulation: Filtering, sorting, formatting output
- Remote management: Managing multiple servers
- Cloud operations: Azure, AWS, Microsoft 365
- Complex scripting: Anything beyond basic batch files
- API interaction: REST APIs, web services
- Report generation: Creating CSV, HTML, JSON reports
Example Scenario:
# Get all stopped services and export to CSV
Get-Service |
Where-Object {$_.Status -eq 'Stopped'} |
Select-Object Name, DisplayName, StartType |
Export-Csv "StoppedServices.csv" -NoTypeInformation
# This would be extremely difficult in CMD!
2.5 PowerShell's Key Advantages
2.5.1 Advantage #1: Discoverability
PowerShell is designed to be discoverable. You can explore commands without documentation:
# Find all commands related to processes
Get-Command *Process*
# Get help for any command
Get-Help Get-Process
# See examples
Get-Help Get-Process -Examples
# View all properties of an object
Get-Process | Get-Member
CMD equivalent: You'd need to search online or remember command syntax. No built-in discovery mechanism.
2.5.2 Advantage #2: Consistent Naming Convention
PowerShell uses a Verb-Noun naming pattern that makes commands intuitive:
| Verb | Purpose | Examples |
|---|---|---|
| Get | Retrieve information | Get-Process, Get-Service, Get-ChildItem |
| Set | Change settings | Set-Location, Set-Content, Set-ExecutionPolicy |
| New | Create new items | New-Item, New-Service, New-ADUser |
| Remove | Delete items | Remove-Item, Remove-Service, Remove-ADUser |
| Start | Begin operations | Start-Process, Start-Service, Start-Job |
| Stop | End operations | Stop-Process, Stop-Service, Stop-Computer |
CMD commands: Inconsistent naming (dir, cd, copy, del, type, tasklist, taskkill, etc.)
2.5.3 Advantage #3: Pipeline Power
PowerShell's object-based pipeline allows you to chain commands effortlessly:
# Find top 5 memory-consuming processes
Get-Process |
Sort-Object WorkingSet -Descending |
Select-Object -First 5 Name, @{Name="MemoryMB";Expression={[math]::Round($_.WorkingSet/1MB,2)}}
# Find all log files modified in last 7 days and compress them
Get-ChildItem -Path C:\Logs -Filter *.log -Recurse |
Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)} |
Compress-Archive -DestinationPath "RecentLogs.zip"
2.5.4 Advantage #4: Remote Management
PowerShell Remoting allows you to manage multiple computers from one location:
# Run command on remote computer
Invoke-Command -ComputerName Server01 -ScriptBlock {Get-Service}
# Manage multiple servers at once
$servers = "Server01", "Server02", "Server03"
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-EventLog -LogName System -Newest 10
}
CMD equivalent: PsExec (third-party tool) or manual RDP connections
2.6 Real-World Comparison Examples
Example 1: Find and Delete Old Files
Task: Delete files older than 30 days from C:\Temp
Command Prompt:
forfiles /p "C:\Temp" /s /m *.* /d -30 /c "cmd /c del @path"
PowerShell:
Get-ChildItem -Path C:\Temp -Recurse |
Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} |
Remove-Item -Force
Winner: PowerShell - More readable, easier to modify (change days, add conditions, etc.)
Example 2: Export Running Services to CSV
Command Prompt:
REM Very limited - can only output text
sc query state= all > services.txt
PowerShell:
Get-Service |
Select-Object Name, DisplayName, Status, StartType |
Export-Csv "Services.csv" -NoTypeInformation
Winner: PowerShell - Proper CSV format, easily imported into Excel
Example 3: Check Disk Space on Multiple Drives
Command Prompt:
REM Text output, hard to parse
wmic logicaldisk get caption,freespace,size
PowerShell:
Get-PSDrive -PSProvider FileSystem |
Where-Object {$_.Used -gt 0} |
Select-Object Name,
@{Name="UsedGB";Expression={[math]::Round($_.Used/1GB,2)}},
@{Name="FreeGB";Expression={[math]::Round($_.Free/1GB,2)}},
@{Name="TotalGB";Expression={[math]::Round(($_.Used+$_.Free)/1GB,2)}},
@{Name="PercentFree";Expression={[math]::Round(($_.Free/($_.Used+$_.Free))*100,2)}}
Winner: PowerShell - Formatted output with calculated fields
2.7 Migration Strategy: Moving from CMD to PowerShell
2.7.1 The Gradual Approach (Recommended)
- Week 1-2: Use PowerShell for simple commands (dir, cd, copy)
- Week 3-4: Learn Get-Command, Get-Help, Get-Member
- Month 2: Replace one batch file with PowerShell script
- Month 3: Learn pipeline and object manipulation
- Month 4+: Tackle advanced scenarios (remoting, automation)
2.7.2 Common Pitfalls to Avoid
β οΈ Common Mistakes:
- Using CMD syntax in PowerShell:
dir /swon't workβuseGet-ChildItem -Recurse - Ignoring execution policy: Scripts won't run without proper policy
- Not using proper error handling: Always use
-ErrorActionparameter - Treating output as text: Remember, PowerShell outputs objects!
- Not learning the pipeline: This is PowerShell's superpower
2.8 Performance Comparison
Startup Time
| Shell | Cold Start | Warm Start |
|---|---|---|
| Command Prompt | ~50ms | ~30ms |
| PowerShell 5.1 | ~800ms | ~400ms |
| PowerShell 7 | ~600ms | ~300ms |
Verdict: CMD starts faster, but for any real work, PowerShell's capabilities far outweigh the startup time difference.
2.9 Hands-On Exercises
Exercise 1: Basic Command Translation
Task: Translate this CMD command to PowerShell:
dir /s /b *.txt
Click to see solution
# PowerShell equivalent
Get-ChildItem -Recurse -Filter *.txt | Select-Object -ExpandProperty FullName
# Or shorter:
Get-ChildItem -Recurse -Filter *.txt | % FullName
Explanation:
/sbecomes-Recurse/b(bare format) becomesSelect-Object -ExpandProperty FullName*.txtbecomes-Filter *.txt
Exercise 2: Find Large Files
Task: Find all files larger than 50MB in C:\Windows and display their name and size in MB.
Click to see solution
Get-ChildItem -Path C:\Windows -Recurse -File -ErrorAction SilentlyContinue |
Where-Object {$_.Length -gt 50MB} |
Select-Object Name, @{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}} |
Sort-Object SizeMB -Descending
Exercise 3: Service Status Report
Task: Get all services that are set to Automatic but are currently stopped.
Click to see solution
Get-Service |
Where-Object {$_.StartType -eq 'Automatic' -and $_.Status -eq 'Stopped'} |
Select-Object Name, DisplayName, Status, StartType
Exercise 4: Process Memory Usage
Task: Find the top 5 processes using the most memory and display in MB.
Click to see solution
Get-Process |
Sort-Object WorkingSet -Descending |
Select-Object -First 5 Name, Id, @{Name="MemoryMB";Expression={[math]::Round($_.WorkingSet/1MB,2)}}
Exercise 5: File Count by Extension
Task: Count how many files of each type exist in your Documents folder.
Click to see solution
Get-ChildItem -Path $env:USERPROFILE\Documents -Recurse -File -ErrorAction SilentlyContinue |
Group-Object Extension |
Select-Object Name, Count |
Sort-Object Count -Descending
2.10 Chapter Summary
π― Key Takeaways
- β Fundamental Difference: CMD works with text, PowerShell works with objects
- β PowerShell Advantages: Object pipeline, discoverability, consistency, remote management
- β When to Use CMD: Simple one-off commands, legacy compatibility
- β When to Use PowerShell: Automation, administration, data manipulation, anything complex
- β Migration Strategy: Gradual transition, learn basics first, practice regularly
- β Naming Convention: Verb-Noun pattern makes PowerShell intuitive
- β Future-Proof: PowerShell is actively developed; CMD is legacy
While Command Prompt still has its place for quick, simple tasks, PowerShell is the clear choice for any serious Windows administration or automation work. The initial learning curve pays off quickly with increased productivity and capabilities.
2.11 Chapter Quiz
Question 1: What is the fundamental difference between CMD and PowerShell?
A) PowerShell is faster
B) CMD works with text, PowerShell works with objects
C) PowerShell has a better interface
D) CMD is more secure
Question 2: What is PowerShell's naming convention for commands?
A) Noun-Verb
B) Verb-Noun
C) Action-Object
D) Command-Parameter
Question 3: Which command shows all properties and methods of an object in PowerShell?
A) Get-Properties
B) Show-Object
C) Get-Member
D) List-Methods
Question 4: True or False: PowerShell can run CMD commands.
Question 5: What is the PowerShell equivalent of the CMD 'dir' command?
A) List-Directory
B) Get-ChildItem
C) Show-Files
D) Read-Directory
Question 6: Which feature allows PowerShell to manage remote computers?
A) Remote Desktop
B) PowerShell Remoting
C) SSH
D) Telnet
Question 7: What symbol is used for the pipeline in PowerShell?
A) > (greater than)
B) | (pipe)
C) & (ampersand)
D) ; (semicolon)
Question 8: Which is faster to start: CMD or PowerShell?
A) CMD
B) PowerShell
C) They're the same
D) Depends on the computer
Question 9: What framework is PowerShell built on?
A) Java
B) .NET
C) Python
D) C++
Question 10: When should you use CMD instead of PowerShell?
A) For all administrative tasks
B) For automation scripts
C) For simple one-off commands and legacy compatibility
D) Never, PowerShell is always better
Chapter 3: DOS to PowerShell Translation Guide
π Learning Objectives
By the end of this chapter, you will be able to:
- Translate any DOS/CMD command to PowerShell equivalent
- Understand the logic behind PowerShell command naming
- Use 50+ essential command translations
- Migrate existing batch scripts to PowerShell
- Leverage PowerShell's enhanced capabilities
3.1 Introduction: Why This Translation Matters
If you've been using DOS or Command Prompt for years, you have valuable knowledge! This chapter helps you leverage that experience by showing you the PowerShell equivalents of every command you already know.
3.2 Understanding PowerShell Aliases
PowerShell includes built-in aliases that mimic DOS commands, making the transition easier. However, it's best practice to learn the full cmdlet names for scripts.
What are Aliases?
Aliases are shortcuts to PowerShell cmdlets. For example:
# These all do the same thing:
dir # DOS alias
ls # Linux alias
Get-ChildItem # Full PowerShell cmdlet (RECOMMENDED)
# View all aliases
Get-Alias
# Find what a specific alias does
Get-Alias dir
Get-Alias ls
3.3 The Complete Translation Table
Here's your comprehensive guide to translating DOS commands to PowerShell. This table includes 50+ essential commands organized by category.
3.3.1 File and Directory Operations
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
dir |
Get-ChildItem |
dir, ls, gci |
List directory contents |
cd |
Set-Location |
cd, sl |
Change directory |
cd.. |
Set-Location .. |
cd .. |
Go up one directory |
md, mkdir |
New-Item -ItemType Directory |
mkdir, md |
Create new directory |
rd, rmdir |
Remove-Item |
rm, del, rmdir |
Remove directory |
copy |
Copy-Item |
copy, cp |
Copy files/folders |
move |
Move-Item |
move, mv |
Move files/folders |
ren, rename |
Rename-Item |
ren |
Rename files/folders |
del, erase |
Remove-Item |
del, rm |
Delete files |
type |
Get-Content |
type, cat, gc |
Display file contents |
tree |
Get-ChildItem -Recurse |
tree |
Display directory tree |
attrib |
Get-ItemProperty |
- | View/modify file attributes |
3.3.2 System Information Commands
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
hostname |
$env:COMPUTERNAME |
hostname |
Display computer name |
ver |
$PSVersionTable |
- | Display version info |
systeminfo |
Get-ComputerInfo |
- | Display system information |
date |
Get-Date |
date |
Display/set date |
time |
Get-Date |
time |
Display/set time |
set |
Get-ChildItem Env: |
set, env |
Display environment variables |
path |
$env:PATH |
- | Display PATH variable |
echo |
Write-Output |
echo, write |
Display text |
3.3.3 Process Management
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
tasklist |
Get-Process |
ps, gps |
List running processes |
taskkill |
Stop-Process |
kill |
Stop/kill a process |
start |
Start-Process |
start |
Start a process |
3.3.4 Network Commands
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
ipconfig |
Get-NetIPAddress |
ipconfig |
Display IP configuration |
ping |
Test-Connection |
ping |
Test network connectivity |
tracert |
Test-NetConnection -TraceRoute |
- | Trace route to host |
nslookup |
Resolve-DnsName |
nslookup |
DNS lookup |
netstat |
Get-NetTCPConnection |
netstat |
Display network connections |
net user |
Get-LocalUser |
- | Manage local users |
net share |
Get-SmbShare |
- | Display shared folders |
3.3.5 Disk Management
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
chkdsk |
Repair-Volume |
chkdsk |
Check disk for errors |
format |
Format-Volume |
format |
Format a disk |
diskpart |
Get-Disk, Get-Partition |
- | Disk partitioning |
vol |
Get-Volume |
- | Display volume label |
3.3.6 Service Management
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
sc query |
Get-Service |
gsv |
List services |
sc start |
Start-Service |
sasv |
Start a service |
sc stop |
Stop-Service |
spsv |
Stop a service |
sc config |
Set-Service |
- | Configure a service |
3.3.7 Text Processing & Search
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
find |
Select-String |
sls |
Search for text in files |
findstr |
Select-String |
sls |
Advanced text search |
sort |
Sort-Object |
sort |
Sort output |
more |
Out-Host -Paging |
more |
Page through output |
3.3.8 Miscellaneous Commands
| DOS/CMD Command | PowerShell Cmdlet | Alias | Description |
|---|---|---|---|
cls |
Clear-Host |
cls, clear |
Clear screen |
exit |
Exit |
exit |
Exit PowerShell |
help |
Get-Help |
help, man |
Display help |
pause |
Read-Host "Press Enter" |
pause |
Pause execution |
title |
$Host.UI.RawUI.WindowTitle |
- | Set window title |
color |
$Host.UI.RawUI.BackgroundColor |
- | Change console colors |
prompt |
function prompt {} |
- | Customize prompt |
3.4 Side-by-Side Comparison Examples
Let's see real-world examples comparing DOS and PowerShell commands:
Example 1: List Files
DOS/CMD
REM List all files
dir
REM List with details
dir /a
REM List only files (no directories)
dir /a-d
REM List subdirectories recursively
dir /s
PowerShell
# List all files
Get-ChildItem
# List with details (already default)
Get-ChildItem | Format-List
# List only files (no directories)
Get-ChildItem -File
# List subdirectories recursively
Get-ChildItem -Recurse
Example 2: Find Large Files
DOS/CMD (Complex)
REM Very difficult in CMD
REM Requires FOR loops and string parsing
FOR /R %i IN (*) DO @IF %~zi GTR 10485760 ECHO %i %~zi
PowerShell (Simple)
# Find files larger than 10MB
Get-ChildItem -Recurse |
Where-Object {$_.Length -gt 10MB} |
Select-Object Name, @{N="SizeMB";E={[math]::Round($_.Length/1MB,2)}} |
Sort-Object SizeMB -Descending
Example 3: Process Management
DOS/CMD
REM List processes
tasklist
REM Find specific process
tasklist | find "chrome"
REM Kill a process
taskkill /IM chrome.exe /F
PowerShell
# List processes
Get-Process
# Find specific process
Get-Process chrome
# Kill a process
Stop-Process -Name chrome
# Kill with confirmation
Stop-Process -Name chrome -Confirm
Example 4: Network Testing
DOS/CMD
REM Ping a host
ping google.com
REM Ping with count
ping -n 4 google.com
REM Get IP config
ipconfig /all
PowerShell
# Ping a host (returns object)
Test-Connection google.com
# Ping with count
Test-Connection google.com -Count 4
# Get IP config (structured data)
Get-NetIPAddress | Format-Table
Example 5: File Search
DOS/CMD
REM Find files by name
dir /s /b *.txt
REM Search for text in files
findstr /s /i "error" *.log
PowerShell
# Find files by name
Get-ChildItem -Recurse -Filter *.txt
# Search for text in files
Get-ChildItem -Recurse -Filter *.log |
Select-String -Pattern "error" -CaseSensitive:$false
3.5 PowerShell Enhancements Over DOS
PowerShell doesn't just replace DOS commandsβit enhances them. Here are key improvements:
Enhancement 1: Object Output
# DOS returns text, PowerShell returns objects
$processes = Get-Process
# Access properties directly
$processes[0].Name
$processes[0].CPU
$processes[0].WorkingSet
# Filter and sort easily
$processes | Where-Object {$_.CPU -gt 10} | Sort-Object CPU -Descending
Enhancement 2: Pipeline Power
# Chain commands together
Get-Process |
Where-Object {$_.WorkingSet -gt 100MB} |
Sort-Object WorkingSet -Descending |
Select-Object -First 10 Name, @{N="MemoryMB";E={[math]::Round($_.WorkingSet/1MB,2)}} |
Export-Csv "TopProcesses.csv"
Enhancement 3: Remote Capabilities
# Execute commands on remote computers
Invoke-Command -ComputerName Server01 -ScriptBlock {
Get-Service | Where-Object {$_.Status -eq "Running"}
}
# Get processes from multiple computers
Get-Process -ComputerName Server01, Server02, Server03
Enhancement 4: Error Handling
# Proper error handling
try {
Get-ChildItem C:\Windows\System32 -Recurse -ErrorAction Stop
}
catch {
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
}
# Continue on errors
Get-ChildItem C:\Windows -Recurse -ErrorAction SilentlyContinue
3.6 Migration Strategy: From Batch to PowerShell
Ready to migrate your batch scripts? Follow this systematic approach:
Step 1: Identify Commands
List all DOS commands used in your batch file and find their PowerShell equivalents using the tables above.
Step 2: Understand the Logic
PowerShell uses different syntax for loops, conditionals, and variables:
| Concept | Batch Script | PowerShell |
|---|---|---|
| Variables | SET var=value |
$var = "value" |
| If Statement | IF condition (command) |
if ($condition) { command } |
| For Loop | FOR %%i IN (...) DO |
foreach ($i in ...) { } |
| Comments | REM comment |
# comment |
| Echo | ECHO text |
Write-Host "text" |
Step 3: Rewrite with PowerShell Best Practices
Batch Script
@ECHO OFF
REM Backup script
SET SOURCE=C:\Data
SET DEST=D:\Backup
SET DATE=%DATE:~-4,4%%DATE:~-10,2%%DATE:~-7,2%
ECHO Starting backup...
XCOPY %SOURCE% %DEST%\%DATE% /E /I /Y
IF %ERRORLEVEL% EQU 0 (
ECHO Backup completed successfully
) ELSE (
ECHO Backup failed
)
PAUSE
PowerShell Script
# Backup script
$source = "C:\Data"
$dest = "D:\Backup"
$date = Get-Date -Format "yyyyMMdd"
$backupPath = "$dest\$date"
Write-Host "Starting backup..." -ForegroundColor Cyan
try {
Copy-Item -Path $source -Destination $backupPath -Recurse -Force
Write-Host "Backup completed successfully" -ForegroundColor Green
}
catch {
Write-Host "Backup failed: $($_.Exception.Message)" -ForegroundColor Red
}
Read-Host "Press Enter to continue"
3.7 Common Pitfalls and Solutions
Pitfall 1: Assuming Text Output
Problem: Trying to parse PowerShell output as text
# β Wrong (treating as text)
$result = Get-Process
$result[0] # This is an object, not text!
# β
Correct (using object properties)
$result = Get-Process
$result[0].Name
$result[0].CPU
Pitfall 2: Using Aliases in Scripts
Problem: Scripts break on systems with different aliases
# β Wrong (using aliases)
dir | where {$_.Length -gt 1MB}
# β
Correct (using full cmdlet names)
Get-ChildItem | Where-Object {$_.Length -gt 1MB}
Pitfall 3: Ignoring Error Handling
Problem: Scripts fail silently
# β Wrong (no error handling)
Remove-Item C:\ImportantFile.txt
# β
Correct (with error handling)
try {
Remove-Item C:\ImportantFile.txt -ErrorAction Stop
Write-Host "File deleted successfully"
}
catch {
Write-Host "Error: $($_.Exception.Message)"
}
3.8 Quick Reference Card
Print this section for quick lookup:
π Top 20 Most Used Translations
| DOS | PowerShell | Example |
|---|---|---|
dir |
Get-ChildItem |
Get-ChildItem C:\ |
cd |
Set-Location |
Set-Location C:\Windows |
copy |
Copy-Item |
Copy-Item file.txt backup.txt |
del |
Remove-Item |
Remove-Item file.txt |
type |
Get-Content |
Get-Content file.txt |
echo |
Write-Output |
Write-Output "Hello" |
cls |
Clear-Host |
Clear-Host |
tasklist |
Get-Process |
Get-Process chrome |
taskkill |
Stop-Process |
Stop-Process -Name chrome |
ping |
Test-Connection |
Test-Connection google.com |
3.9 Hands-On Exercises
Exercise 1: Basic Translation
Task: Convert this DOS command to PowerShell:
dir /s /b *.txt
Click to see solution
# PowerShell equivalent
Get-ChildItem -Recurse -Filter *.txt | Select-Object -ExpandProperty FullName
# Or shorter with alias
gci -r -fi *.txt | select -exp FullName
Explanation:
/s=-Recurse(search subdirectories)/b=Select-Object -ExpandProperty FullName(bare format)*.txt=-Filter *.txt(file filter)
Exercise 2: Process Management
Task: Convert this batch script to PowerShell:
tasklist | find "chrome"
taskkill /IM chrome.exe /F
Click to see solution
# PowerShell equivalent
Get-Process chrome
Stop-Process -Name chrome -Force
# Or in one line with error handling
Get-Process chrome -ErrorAction SilentlyContinue | Stop-Process -Force
Exercise 3: File Operations
Task: Create a PowerShell script that:
- Lists all .log files in C:\Windows\Logs
- Finds files larger than 1MB
- Copies them to C:\Backup
Click to see solution
# Complete solution
$sourcePath = "C:\Windows\Logs"
$destPath = "C:\Backup"
# Create backup folder if it doesn't exist
if (-not (Test-Path $destPath)) {
New-Item -ItemType Directory -Path $destPath
}
# Find and copy large log files
Get-ChildItem -Path $sourcePath -Filter *.log -Recurse |
Where-Object {$_.Length -gt 1MB} |
ForEach-Object {
Copy-Item -Path $_.FullName -Destination $destPath
Write-Host "Copied: $($_.Name) - Size: $([math]::Round($_.Length/1MB,2)) MB"
}
Exercise 4: System Information
Task: Get computer name, OS version, and free disk space using PowerShell
Click to see solution
# System information script
$computerName = $env:COMPUTERNAME
$osVersion = (Get-CimInstance Win32_OperatingSystem).Caption
$diskInfo = Get-PSDrive C | Select-Object @{N="FreeGB";E={[math]::Round($_.Free/1GB,2)}}
Write-Host "Computer Name: $computerName"
Write-Host "OS Version: $osVersion"
Write-Host "Free Space on C: $($diskInfo.FreeGB) GB"
Exercise 5: Batch to PowerShell Conversion
Task: Convert this complete batch script:
@ECHO OFF
SET LOGFILE=backup.log
ECHO Backup started at %TIME% >> %LOGFILE%
XCOPY C:\Data D:\Backup /E /I /Y
ECHO Backup completed at %TIME% >> %LOGFILE%
Click to see solution
# PowerShell version
$logFile = "backup.log"
$startTime = Get-Date
# Log start
Add-Content -Path $logFile -Value "Backup started at $startTime"
# Perform backup
try {
Copy-Item -Path "C:\Data" -Destination "D:\Backup" -Recurse -Force
$endTime = Get-Date
Add-Content -Path $logFile -Value "Backup completed at $endTime"
# Calculate duration
$duration = $endTime - $startTime
Add-Content -Path $logFile -Value "Duration: $($duration.TotalMinutes) minutes"
}
catch {
Add-Content -Path $logFile -Value "Backup failed: $($_.Exception.Message)"
}
3.10 Chapter Summary
Key Takeaways
- β PowerShell has equivalents for all DOS/CMD commands
- β Aliases provide backward compatibility but shouldn't be used in scripts
- β PowerShell commands follow Verb-Noun naming convention
- β PowerShell returns objects, not text, enabling powerful data manipulation
- β Migration from batch to PowerShell requires understanding syntax differences
- β PowerShell offers enhanced capabilities: error handling, remoting, pipeline
- β
Use
Get-Aliasto discover command equivalents - β Always use full cmdlet names in production scripts
What You've Learned
- 50+ DOS to PowerShell command translations
- How to use PowerShell aliases effectively
- Side-by-side comparison of common tasks
- Migration strategy from batch scripts to PowerShell
- Common pitfalls and how to avoid them
- PowerShell enhancements over DOS
3.11 Chapter Quiz
Question 1: What is the PowerShell equivalent of the DOS "dir" command?
A) List-Directory B) Get-ChildItem C) Show-Files D) Display-Directory
Question 2: Should you use aliases in production PowerShell scripts?
A) Yes, always B) No, never C) Only for common commands D) It doesn't matter
Question 3: What command shows all available aliases?
A) Show-Alias B) List-Alias C) Get-Alias D) Display-Alias
Question 4: What is the main advantage of PowerShell over DOS?
A) Faster execution B) Object-based output C) Smaller commands D) Easier to type
Question 5: How do you list only files (not directories) in PowerShell?
A) Get-ChildItem -Files B) Get-ChildItem -File C) Get-ChildItem -OnlyFiles D) Get-ChildItem -NoDirectories
Question 6: What is the PowerShell equivalent of "tasklist"?
A) List-Process B) Show-Process C) Get-Process D) Display-Process
Question 7: How do you search for text in files using PowerShell?
A) Find-String B) Search-Content C) Select-String D) Get-Text
Question 8: What symbol is used for comments in PowerShell?
A) REM B) // C) # D) /* */
Question 9: How do you declare a variable in PowerShell?
A) SET var=value B) $var = value C) var = value D) DECLARE var value
Question 10: What is the PowerShell equivalent of "ipconfig"?
A) Get-IPConfig B) Get-NetIPAddress C) Show-IPAddress D) Display-Network
π Quick Reference Resources
- View all aliases:
Get-Alias - Find cmdlet for alias:
Get-Alias dir - Find aliases for cmdlet:
Get-Alias -Definition Get-ChildItem - Get help:
Get-Help Get-ChildItem -Examples - Update help:
Update-Help(run as admin)
Chapter 4: Setting Up Your PowerShell Environment
π Learning Objectives
By the end of this chapter, you will be able to:
- Install and configure PowerShell 7+ on Windows, Linux, and macOS
- Understand and configure execution policies
- Create and customize PowerShell profiles
- Install and manage PowerShell modules
- Set up Visual Studio Code for PowerShell development
- Configure your environment for maximum productivity
4.1 Understanding PowerShell Versions
Before setting up your environment, it's important to understand the different PowerShell versions available in 2026:
| Version | Name | Platforms | Status | Recommendation |
|---|---|---|---|---|
| 5.1 | Windows PowerShell | Windows only | Maintenance mode | β οΈ Legacy systems only |
| 7.0-7.4 | PowerShell Core | Cross-platform | Supported | β Good for production |
| 7.5+ | PowerShell | Cross-platform | Current | β Recommended for new projects |
4.2 Installing PowerShell 7+
4.2.1 Installation on Windows
Method 1: Using Windows Package Manager (Recommended)
# Install using winget (Windows 11/10)
winget install Microsoft.PowerShell
# Verify installation
pwsh --version
Method 2: Using MSI Installer
- Visit:
https://github.com/PowerShell/PowerShell/releases - Download the latest
.msifile for Windows - Run the installer with default options
- Restart your terminal
Method 3: Using Chocolatey
# Install using Chocolatey
choco install powershell-core -y
# Verify installation
pwsh --version
4.2.2 Installation on Linux
Ubuntu/Debian:
# Update package list
sudo apt-get update
# Install prerequisites
sudo apt-get install -y wget apt-transport-https software-properties-common
# Download Microsoft repository GPG keys
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
# Register the repository
sudo dpkg -i packages-microsoft-prod.deb
# Install PowerShell
sudo apt-get update
sudo apt-get install -y powershell
# Start PowerShell
pwsh
Red Hat/CentOS:
# Register Microsoft repository
curl https://packages.microsoft.com/config/rhel/8/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo
# Install PowerShell
sudo yum install -y powershell
# Start PowerShell
pwsh
4.2.3 Installation on macOS
Using Homebrew (Recommended):
# Install Homebrew if not already installed
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Install PowerShell
brew install --cask powershell
# Start PowerShell
pwsh
4.2.4 Verifying Your Installation
# Check PowerShell version
$PSVersionTable
# Expected output:
# Name Value
# ---- -----
# PSVersion 7.5.0
# PSEdition Core
# GitCommitId 7.5.0
# OS Microsoft Windows 10.0.22631
# Platform Win32NT
# PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
# PSRemotingProtocolVersion 2.3
# SerializationVersion 1.1.0.1
# WSManStackVersion 3.0
4.3 Understanding Execution Policies
Execution policies are a security feature that controls the conditions under which PowerShell loads configuration files and runs scripts.
4.3.1 Execution Policy Levels
| Policy | Description | Use Case |
|---|---|---|
| Restricted | No scripts allowed (default on Windows) | Maximum security, interactive only |
| AllSigned | Only signed scripts by trusted publisher | Enterprise environments |
| RemoteSigned | Local scripts OK, remote scripts must be signed | β Recommended for most users |
| Unrestricted | All scripts run, warns on remote scripts | Development environments |
| Bypass | No restrictions, no warnings | Automation scripts only |
4.3.2 Checking and Setting Execution Policy
# Check current execution policy
Get-ExecutionPolicy
# Check execution policy for all scopes
Get-ExecutionPolicy -List
# Set execution policy for current user (Recommended)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Set execution policy for local machine (Requires Admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
Unrestricted or Bypass on production systems. Use RemoteSigned for the best balance between security and usability.
4.3.3 Execution Policy Scopes
| Scope | Description | Priority |
|---|---|---|
| MachinePolicy | Set by Group Policy for all users | 1 (Highest) |
| UserPolicy | Set by Group Policy for current user | 2 |
| Process | Current PowerShell session only | 3 |
| CurrentUser | Current user only | 4 |
| LocalMachine | All users on the computer | 5 (Lowest) |
4.4 PowerShell Profiles
PowerShell profiles are scripts that run automatically when you start PowerShell. They're perfect for customizing your environment.
4.4.1 Understanding Profile Types
| Profile | Description | Path |
|---|---|---|
| Current User, Current Host | Most commonly used | $PROFILE.CurrentUserCurrentHost |
| Current User, All Hosts | All PowerShell hosts | $PROFILE.CurrentUserAllHosts |
| All Users, Current Host | All users, specific host | $PROFILE.AllUsersCurrentHost |
| All Users, All Hosts | All users, all hosts | $PROFILE.AllUsersAllHosts |
4.4.2 Creating Your Profile
# Check if profile exists
Test-Path $PROFILE
# View profile path
$PROFILE
# Create profile if it doesn't exist
if (!(Test-Path $PROFILE)) {
New-Item -Path $PROFILE -ItemType File -Force
}
# Open profile in notepad
notepad $PROFILE
# Or open in VS Code (if installed)
code $PROFILE
4.4.3 Sample Profile Configuration
Here's a comprehensive profile configuration you can use:
# ============================================
# PowerShell Profile Configuration
# Author: Your Name
# Last Updated: June 2026
# ============================================
# --- Welcome Message ---
Write-Host "Welcome to PowerShell, $env:USERNAME!" -ForegroundColor Cyan
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Green
Write-Host "Today is: $(Get-Date -Format 'dddd, MMMM dd, yyyy')" -ForegroundColor Yellow
Write-Host ""
# --- Set Location ---
# Start in your preferred directory
Set-Location C:\Projects
# --- Custom Aliases ---
Set-Alias -Name np -Value notepad.exe
Set-Alias -Name vs -Value code
Set-Alias -Name ll -Value Get-ChildItem
# --- Custom Functions ---
# Quick directory listing with colors
function Get-DirectorySize {
Get-ChildItem |
Select-Object Name,
@{Name="Size(MB)";Expression={[math]::Round($_.Length/1MB,2)}},
LastWriteTime |
Sort-Object "Size(MB)" -Descending
}
Set-Alias -Name lls -Value Get-DirectorySize
# Quick system information
function Get-QuickInfo {
$os = Get-CimInstance Win32_OperatingSystem
$cpu = Get-CimInstance Win32_Processor
$mem = Get-CimInstance Win32_PhysicalMemory
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
OS = $os.Caption
Version = $os.Version
CPU = $cpu.Name
Cores = $cpu.NumberOfCores
RAM_GB = [math]::Round(($mem | Measure-Object Capacity -Sum).Sum / 1GB, 2)
Uptime = (Get-Date) - $os.LastBootUpTime
}
}
Set-Alias -Name sysinfo -Value Get-QuickInfo
# Quick network test
function Test-QuickConnection {
param([string]$Target = "8.8.8.8")
Test-Connection -ComputerName $Target -Count 2 -Quiet
}
Set-Alias -Name qping -Value Test-QuickConnection
# Open current directory in File Explorer
function Open-Explorer {
explorer.exe .
}
Set-Alias -Name open -Value Open-Explorer
# --- Prompt Customization ---
function prompt {
$location = Get-Location
$time = Get-Date -Format "HH:mm:ss"
Write-Host "[$time] " -NoNewline -ForegroundColor Gray
Write-Host "$env:USERNAME" -NoNewline -ForegroundColor Green
Write-Host "@" -NoNewline -ForegroundColor White
Write-Host "$env:COMPUTERNAME" -NoNewline -ForegroundColor Cyan
Write-Host " $location" -ForegroundColor Yellow
Write-Host "PS>" -NoNewline -ForegroundColor Magenta
return " "
}
# --- Module Auto-Import ---
# Import commonly used modules
$modules = @('PSReadLine', 'Posh-Git')
foreach ($module in $modules) {
if (Get-Module -ListAvailable -Name $module) {
Import-Module $module -ErrorAction SilentlyContinue
}
}
# --- PSReadLine Configuration (Enhanced Command Line) ---
if (Get-Module -ListAvailable -Name PSReadLine) {
# Prediction settings
Set-PSReadLineOption -PredictionSource History
Set-PSReadLineOption -PredictionViewStyle ListView
# Colors
Set-PSReadLineOption -Colors @{
Command = 'Green'
Parameter = 'Gray'
String = 'Yellow'
}
# Key bindings
Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
}
# --- Environment Variables ---
$env:EDITOR = "code" # Default editor
# --- Completion ---
# Enable tab completion for common commands
Register-ArgumentCompleter -CommandName Get-Service -ParameterName Name -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
Get-Service "$wordToComplete*" | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Name)
}
}
Write-Host "Profile loaded successfully!" -ForegroundColor Green
Write-Host "Type 'Get-Command -Module $($MyInvocation.MyCommand.Module)' to see custom commands" -ForegroundColor Gray
Write-Host ""
4.4.4 Reload Profile Without Restarting
# Reload your profile
. $PROFILE
# Or create a function in your profile to reload it
function Reload-Profile {
. $PROFILE
Write-Host "Profile reloaded!" -ForegroundColor Green
}
4.5 Module Management
4.5.1 Understanding Modules
Modules are packages of PowerShell functionality. They contain cmdlets, functions, variables, and more.
# List all installed modules
Get-Module -ListAvailable
# List currently loaded modules
Get-Module
# Find modules in PowerShell Gallery
Find-Module -Name "*Azure*"
# Get module information
Get-Module -Name PSReadLine -ListAvailable
4.5.2 Installing Modules
# Install a module from PowerShell Gallery
Install-Module -Name PSReadLine -Scope CurrentUser
# Install specific version
Install-Module -Name Az -RequiredVersion 9.0.0 -Scope CurrentUser
# Install module with dependencies
Install-Module -Name Posh-Git -Scope CurrentUser -Force
# Update all installed modules
Update-Module
# Update specific module
Update-Module -Name PSReadLine
4.5.3 Essential Modules to Install
| Module | Description | Install Command |
|---|---|---|
| PSReadLine | Enhanced command-line editing | Install-Module PSReadLine |
| Posh-Git | Git integration for prompt | Install-Module Posh-Git |
| Az | Azure PowerShell module | Install-Module Az |
| Microsoft.Graph | Microsoft Graph API | Install-Module Microsoft.Graph |
| ImportExcel | Excel file manipulation | Install-Module ImportExcel |
| PSScriptAnalyzer | Script quality checker | Install-Module PSScriptAnalyzer |
4.5.4 Managing Modules
# Import a module
Import-Module -Name PSReadLine
# Remove a module from current session
Remove-Module -Name PSReadLine
# Uninstall a module
Uninstall-Module -Name OldModule
# List commands in a module
Get-Command -Module PSReadLine
# Get module path
$env:PSModulePath -split ';'
4.6 Setting Up Visual Studio Code
4.6.1 Installing VS Code
Windows:
winget install Microsoft.VisualStudioCode
Linux:
sudo snap install code --classic
macOS:
brew install --cask visual-studio-code
4.6.2 Essential VS Code Extensions for PowerShell
| Extension | Description | Install Command |
|---|---|---|
| PowerShell | Official PowerShell extension (MUST HAVE) | code --install-extension ms-vscode.powershell |
| PowerShell Preview | Latest PowerShell features | code --install-extension ms-vscode.powershell-preview |
| Code Spell Checker | Spell checking in comments | code --install-extension streetsidesoftware.code-spell-checker |
| GitLens | Enhanced Git integration | code --install-extension eamodio.gitlens |
| Bracket Pair Colorizer | Color-coded brackets | code --install-extension CoenraadS.bracket-pair-colorizer-2 |
4.6.3 VS Code Settings for PowerShell
Press Ctrl+, (or Cmd+, on Mac) to open settings, then add:
{
// PowerShell settings
"powershell.powerShellDefaultVersion": "PowerShell (x64)",
"powershell.integratedConsole.showOnStartup": false,
"powershell.codeFormatting.preset": "OTBS",
"powershell.codeFormatting.autoCorrectAliases": true,
"powershell.codeFormatting.useCorrectCasing": true,
// Editor settings
"editor.fontSize": 14,
"editor.fontFamily": "'Cascadia Code', 'Fira Code', Consolas, monospace",
"editor.fontLigatures": true,
"editor.minimap.enabled": true,
"editor.rulers": [80, 120],
"editor.renderWhitespace": "boundary",
"editor.formatOnSave": true,
// Terminal settings
"terminal.integrated.fontSize": 13,
"terminal.integrated.fontFamily": "'Cascadia Code', Consolas",
"terminal.integrated.defaultProfile.windows": "PowerShell",
// Files settings
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 1000,
"files.trimTrailingWhitespace": true,
// Color theme (optional)
"workbench.colorTheme": "PowerShell ISE",
"workbench.iconTheme": "vs-seti"
}
4.6.4 Useful VS Code Keyboard Shortcuts
| Action | Windows/Linux | macOS |
|---|---|---|
| Run current file | F5 |
F5 |
| Run selection | F8 |
F8 |
| Toggle terminal | Ctrl+` |
Cmd+` |
| Command palette | Ctrl+Shift+P |
Cmd+Shift+P |
| Quick open file | Ctrl+P |
Cmd+P |
| Format document | Shift+Alt+F |
Shift+Option+F |
| Comment/Uncomment | Ctrl+/ |
Cmd+/ |
4.7 Windows Terminal Configuration
4.7.1 Installing Windows Terminal
# Install using winget
winget install Microsoft.WindowsTerminal
# Or install from Microsoft Store
# Search for "Windows Terminal"
4.7.2 Customizing Windows Terminal
Press Ctrl+, to open settings, or edit the JSON file directly:
{
"defaultProfile": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"profiles": {
"defaults": {
"fontFace": "Cascadia Code PL",
"fontSize": 11,
"colorScheme": "One Half Dark",
"useAcrylic": true,
"acrylicOpacity": 0.9,
"cursorShape": "bar"
},
"list": [
{
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"name": "PowerShell 7",
"source": "Windows.Terminal.PowershellCore",
"icon": "ms-appx:///ProfileIcons/{574e775e-4f2a-5b96-ac1e-a2962a402336}.png",
"startingDirectory": "%USERPROFILE%"
}
]
},
"schemes": [
{
"name": "PowerShell Custom",
"background": "#012456",
"foreground": "#CCCCCC",
"black": "#0C0C0C",
"blue": "#0037DA",
"cyan": "#3A96DD",
"green": "#13A10E",
"purple": "#881798",
"red": "#C50F1F",
"white": "#CCCCCC",
"yellow": "#C19C00",
"brightBlack": "#767676",
"brightBlue": "#3B78FF",
"brightCyan": "#61D6D6",
"brightGreen": "#16C60C",
"brightPurple": "#B4009E",
"brightRed": "#E74856",
"brightWhite": "#F2F2F2",
"brightYellow": "#F9F1A5"
}
]
}
4.8 Hands-On Exercises
Exercise 1: Install PowerShell 7+
Task: Install the latest version of PowerShell on your system and verify the installation.
Steps:
- Choose the appropriate installation method for your OS
- Install PowerShell 7+
- Open PowerShell and run
$PSVersionTable - Verify PSVersion is 7.0 or higher
Click to see solution
# Windows
winget install Microsoft.PowerShell
# Verify
pwsh
$PSVersionTable
# Expected: PSVersion should be 7.x
Exercise 2: Configure Execution Policy
Task: Set the execution policy to RemoteSigned for the current user.
Click to see solution
# Check current policy
Get-ExecutionPolicy
# Set to RemoteSigned
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Verify
Get-ExecutionPolicy -List
Exercise 3: Create Your PowerShell Profile
Task: Create a PowerShell profile with a custom welcome message and at least 2 custom functions.
Click to see solution
# Create profile
if (!(Test-Path $PROFILE)) {
New-Item -Path $PROFILE -ItemType File -Force
}
# Add content to profile
@"
# Welcome message
Write-Host "Welcome, `$env:USERNAME!" -ForegroundColor Cyan
# Custom function 1: Quick directory listing
function ll { Get-ChildItem | Format-Table -AutoSize }
# Custom function 2: Quick system info
function sysinfo {
Get-ComputerInfo | Select-Object CsName, WindowsVersion, OsArchitecture
}
Write-Host "Profile loaded! Try 'll' or 'sysinfo'" -ForegroundColor Green
"@ | Out-File -FilePath $PROFILE -Encoding UTF8
# Reload profile
. $PROFILE
Exercise 4: Install Essential Modules
Task: Install PSReadLine and ImportExcel modules.
Click to see solution
# Install PSReadLine
Install-Module -Name PSReadLine -Scope CurrentUser -Force
# Install ImportExcel
Install-Module -Name ImportExcel -Scope CurrentUser -Force
# Verify installation
Get-Module -Name PSReadLine, ImportExcel -ListAvailable
# Test ImportExcel
Import-Module ImportExcel
Get-Command -Module ImportExcel
Exercise 5: Set Up VS Code
Task: Install VS Code and the PowerShell extension, then create and run a simple script.
Click to see solution
# Install VS Code
winget install Microsoft.VisualStudioCode
# Install PowerShell extension
code --install-extension ms-vscode.powershell
# Create a test script
$testScript = @"
# Test script
Write-Host "Hello from VS Code!" -ForegroundColor Green
Get-Date
Get-ComputerInfo | Select-Object CsName, WindowsVersion
"@
$testScript | Out-File -FilePath "$HOME\Desktop\test.ps1" -Encoding UTF8
# Open in VS Code
code "$HOME\Desktop\test.ps1"
# Run with F5 or F8
4.9 Chapter Summary
Key Takeaways
- β PowerShell 7+ is cross-platform and recommended for new projects
- β
Execution policy
RemoteSignedprovides good security balance - β PowerShell profiles customize your environment automatically
- β Modules extend PowerShell functionality significantly
- β VS Code is the best IDE for PowerShell development
- β Windows Terminal provides modern terminal experience
- β PSReadLine enhances command-line editing experience
- β Proper setup saves hours of work in the long run
4.10 Chapter Quiz
Question 1: What is the recommended execution policy for most users?
A) Restricted B) AllSigned C) RemoteSigned D) Unrestricted
Question 2: Which profile is most commonly used?
A) AllUsersAllHosts B) CurrentUserCurrentHost C) AllUsersCurrentHost D) CurrentUserAllHosts
Question 3: What command checks if your profile exists?
Question 4: Which module enhances command-line editing in PowerShell?
A) Posh-Git B) PSReadLine C) ImportExcel D) Az
Question 5: What is the keyboard shortcut to run the current file in VS Code?
A) F5 B) F8 C) Ctrl+R D) Ctrl+Enter
Question 6: True or False: PowerShell 7+ can only run on Windows.
Question 7: What command installs a module from PowerShell Gallery?
Question 8: Which scope has the highest priority for execution policy?
A) LocalMachine B) CurrentUser C) Process D) MachinePolicy
Question 9: What variable contains your profile path?
Question 10: Which command reloads your profile without restarting PowerShell?
Chapter 5: Essential Commands Every Admin Should Know
π Learning Objectives
By the end of this chapter, you will be able to:
- Master the most frequently used PowerShell cmdlets
- Perform common system administration tasks efficiently
- Use Get-Help effectively to discover and learn commands
- Work with files, processes, services, and network resources
- Combine commands using the pipeline for powerful operations
- Apply filtering and formatting techniques to command output
5.1 The PowerShell Command Structure
Before diving into specific commands, let's understand PowerShell's command structure:
Verb-Noun naming pattern:
- Verb: What action to perform (Get, Set, New, Remove, etc.)
- Noun: What to act upon (Process, Service, Item, etc.)
Get-Process, Stop-Service, New-Item
5.1.1 Common PowerShell Verbs
| Verb | Purpose | Examples |
|---|---|---|
| Get | Retrieve information | Get-Process, Get-Service, Get-ChildItem |
| Set | Modify existing items | Set-Location, Set-ExecutionPolicy |
| New | Create new items | New-Item, New-Service |
| Remove | Delete items | Remove-Item, Remove-Service |
| Start | Begin an operation | Start-Process, Start-Service |
| Stop | End an operation | Stop-Process, Stop-Service |
| Test | Verify or validate | Test-Path, Test-Connection |
| Write | Output information | Write-Host, Write-Output |
5.2 Getting Help: Your Best Friend
The most important command in PowerShell is Get-Help. It's your built-in documentation system.
5.2.1 Using Get-Help
# Update help files (run once, requires internet and admin rights)
Update-Help -Force -ErrorAction SilentlyContinue
# Get help for a command
Get-Help Get-Process
# Get detailed help with examples
Get-Help Get-Process -Detailed
# Get full help including technical details
Get-Help Get-Process -Full
# Get only examples
Get-Help Get-Process -Examples
# Get help online (opens browser with latest documentation)
Get-Help Get-Process -Online
# Search for commands containing a keyword
Get-Help *process*
# Get help about PowerShell concepts
Get-Help about_Variables
Get-Help about_Operators
Get-Help about_Pipelines
5.2.2 Discovering Commands
# Find all available commands
Get-Command
# Find commands with specific verb
Get-Command -Verb Get
# Find commands with specific noun
Get-Command -Noun Process
# Find commands in a specific module
Get-Command -Module Microsoft.PowerShell.Management
# Search for commands by name pattern
Get-Command *service*
# Get detailed information about a command
Get-Command Get-Process | Format-List *
# Find all aliases
Get-Alias
# Find what command an alias represents
Get-Alias dir
Get-Alias ls
5.3 File and Directory Management
Managing files and directories is one of the most common administrative tasks.
5.3.1 Navigation Commands
# Get current location
Get-Location
pwd # Alias
# Change directory
Set-Location C:\Windows
cd C:\Windows # Alias
# Go to parent directory
Set-Location ..
cd ..
# Go to home directory
Set-Location ~
cd ~
# Go back to previous location
Set-Location -
# Push location onto stack (save for later)
Push-Location C:\Temp
# Pop location from stack (return to saved location)
Pop-Location
5.3.2 Listing Files and Directories
# List files in current directory
Get-ChildItem
dir # Alias
ls # Alias
# List files with specific extension
Get-ChildItem *.txt
# List files recursively
Get-ChildItem -Recurse
# List hidden files
Get-ChildItem -Force
# List only directories
Get-ChildItem -Directory
# List only files
Get-ChildItem -File
# List with detailed properties
Get-ChildItem | Format-Table Name, Length, LastWriteTime
# Find large files (over 100MB)
Get-ChildItem -Recurse | Where-Object {$_.Length -gt 100MB} |
Sort-Object Length -Descending |
Select-Object Name, @{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}}
5.3.3 Creating, Copying, Moving, and Deleting
# Create a new directory
New-Item -ItemType Directory -Path "C:\Temp\MyFolder"
mkdir "C:\Temp\MyFolder" # Alias
# Create a new file
New-Item -ItemType File -Path "C:\Temp\test.txt"
# Copy a file
Copy-Item "C:\Temp\test.txt" "C:\Temp\test_backup.txt"
copy "C:\Temp\test.txt" "C:\Temp\test_backup.txt" # Alias
# Copy a directory recursively
Copy-Item "C:\Temp\MyFolder" "C:\Temp\MyFolder_Backup" -Recurse
# Move a file
Move-Item "C:\Temp\test.txt" "C:\Temp\Archive\test.txt"
move "C:\Temp\test.txt" "C:\Temp\Archive\test.txt" # Alias
# Rename a file
Rename-Item "C:\Temp\test.txt" "newname.txt"
# Delete a file
Remove-Item "C:\Temp\test.txt"
del "C:\Temp\test.txt" # Alias
# Delete a directory and all contents
Remove-Item "C:\Temp\MyFolder" -Recurse -Force
# Delete files older than 30 days
Get-ChildItem "C:\Temp\*.log" |
Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} |
Remove-Item
5.3.4 Reading and Writing Files
# Read entire file content
Get-Content "C:\Temp\test.txt"
cat "C:\Temp\test.txt" # Alias
# Read first 10 lines
Get-Content "C:\Temp\test.txt" -TotalCount 10
# Read last 10 lines
Get-Content "C:\Temp\test.txt" -Tail 10
# Monitor file in real-time (like tail -f)
Get-Content "C:\Logs\app.log" -Wait -Tail 20
# Write to a file (overwrites)
"Hello World" | Out-File "C:\Temp\test.txt"
"Hello World" > "C:\Temp\test.txt" # Redirection operator
# Append to a file
"New line" | Out-File "C:\Temp\test.txt" -Append
"New line" >> "C:\Temp\test.txt" # Redirection operator
# Write array to file
$data = @("Line 1", "Line 2", "Line 3")
$data | Out-File "C:\Temp\test.txt"
# Read CSV file
Import-Csv "C:\Temp\data.csv"
# Write to CSV file
$data | Export-Csv "C:\Temp\output.csv" -NoTypeInformation
5.3.5 Testing Paths
# Check if file or directory exists
Test-Path "C:\Temp\test.txt"
# Check if it's a file
Test-Path "C:\Temp\test.txt" -PathType Leaf
# Check if it's a directory
Test-Path "C:\Temp" -PathType Container
# Use in conditional logic
if (Test-Path "C:\Temp\test.txt") {
Write-Host "File exists"
} else {
Write-Host "File does not exist"
}
5.4 Process Management
Managing processes is crucial for system administration and troubleshooting.
5.4.1 Viewing Processes
# List all running processes
Get-Process
# Get specific process by name
Get-Process -Name "notepad"
# Get process by ID
Get-Process -Id 1234
# Get processes sorted by CPU usage
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
# Get processes sorted by memory usage
Get-Process | Sort-Object WS -Descending | Select-Object -First 10
# Get detailed process information
Get-Process | Select-Object Name, Id, CPU,
@{Name="MemoryMB";Expression={[math]::Round($_.WS/1MB,2)}}
# Find processes using more than 100MB of memory
Get-Process | Where-Object {$_.WS -gt 100MB} |
Sort-Object WS -Descending
5.4.2 Starting and Stopping Processes
# Start a process
Start-Process "notepad.exe"
# Start a process with arguments
Start-Process "notepad.exe" -ArgumentList "C:\Temp\test.txt"
# Start a process as administrator
Start-Process "powershell.exe" -Verb RunAs
# Start a process and wait for it to complete
Start-Process "notepad.exe" -Wait
# Stop a process by name
Stop-Process -Name "notepad"
# Stop a process by ID
Stop-Process -Id 1234
# Stop all instances of a process
Get-Process -Name "notepad" | Stop-Process
# Stop a process forcefully
Stop-Process -Name "notepad" -Force
# Stop processes using more than 500MB RAM
Get-Process | Where-Object {$_.WS -gt 500MB} | Stop-Process -Force
5.4.3 Process Monitoring Script
# Monitor CPU usage and alert if high
$threshold = 80
$process = Get-Process | Sort-Object CPU -Descending | Select-Object -First 1
if ($process.CPU -gt $threshold) {
Write-Warning "High CPU usage detected: $($process.Name) using $($process.CPU)%"
}
# Monitor memory usage
$totalMemory = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
$freeMemory = (Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory * 1KB
$usedMemoryPercent = [math]::Round((($totalMemory - $freeMemory) / $totalMemory) * 100, 2)
Write-Host "Memory Usage: $usedMemoryPercent%"
5.5 Service Management
Services are background processes that run continuously on Windows systems.
5.5.1 Viewing Services
# List all services
Get-Service
# Get specific service
Get-Service -Name "wuauserv" # Windows Update service
# Get running services
Get-Service | Where-Object {$_.Status -eq "Running"}
# Get stopped services
Get-Service | Where-Object {$_.Status -eq "Stopped"}
# Get services with specific display name pattern
Get-Service | Where-Object {$_.DisplayName -like "*Windows*"}
# Get service details
Get-Service -Name "wuauserv" | Format-List *
# Get services that can be stopped
Get-Service | Where-Object {$_.CanStop -eq $true}
5.5.2 Starting, Stopping, and Restarting Services
# Start a service
Start-Service -Name "wuauserv"
# Stop a service
Stop-Service -Name "wuauserv"
# Restart a service
Restart-Service -Name "wuauserv"
# Start multiple services
Start-Service -Name "wuauserv", "BITS"
# Stop a service and its dependencies
Stop-Service -Name "wuauserv" -Force
# Set service startup type
Set-Service -Name "wuauserv" -StartupType Automatic
# Options: Automatic, Manual, Disabled
# Start a service and set startup type
Set-Service -Name "wuauserv" -StartupType Automatic -Status Running
Get-Service -Name "ServiceName" | Format-List * to see dependencies.
5.5.3 Service Monitoring Script
# Check if critical services are running
$criticalServices = @("wuauserv", "BITS", "EventLog")
foreach ($service in $criticalServices) {
$status = (Get-Service -Name $service).Status
if ($status -ne "Running") {
Write-Warning "Service $service is $status"
# Optionally start the service
# Start-Service -Name $service
} else {
Write-Host "Service $service is running" -ForegroundColor Green
}
}
# Get services that failed to start
Get-Service | Where-Object {$_.Status -eq "Stopped" -and $_.StartType -eq "Automatic"}
5.6 System Information
Gathering system information is essential for inventory, troubleshooting, and capacity planning.
5.6.1 Computer Information
# Get comprehensive computer information (Windows 10/Server 2016+)
Get-ComputerInfo
# Get specific properties
Get-ComputerInfo | Select-Object CsName, OsName, OsVersion, OsArchitecture
# Get computer name
$env:COMPUTERNAME
hostname
# Get operating system information
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object Caption, Version, BuildNumber, OSArchitecture,
@{Name="InstallDate";Expression={$_.ConvertToDateTime($_.InstallDate)}},
@{Name="LastBootUpTime";Expression={$_.ConvertToDateTime($_.LastBootUpTime)}}
# Calculate system uptime
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime
Write-Host "System Uptime: $($uptime.Days) days, $($uptime.Hours) hours, $($uptime.Minutes) minutes"
5.6.2 Hardware Information
# Get BIOS information
Get-CimInstance -ClassName Win32_BIOS
# Get processor information
Get-CimInstance -ClassName Win32_Processor |
Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed
# Get memory information
Get-CimInstance -ClassName Win32_PhysicalMemory |
Select-Object Manufacturer, Capacity, Speed, DeviceLocator
# Total physical memory
$totalMemoryGB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
Write-Host "Total Physical Memory: $totalMemoryGB GB"
# Get disk information
Get-CimInstance -ClassName Win32_LogicalDisk |
Where-Object {$_.DriveType -eq 3} |
Select-Object DeviceID,
@{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}},
@{Name="FreeSpaceGB";Expression={[math]::Round($_.FreeSpace/1GB,2)}},
@{Name="UsedSpaceGB";Expression={[math]::Round(($_.Size - $_.FreeSpace)/1GB,2)}},
@{Name="PercentFree";Expression={[math]::Round(($_.FreeSpace/$_.Size)*100,2)}}
# Get network adapter information
Get-CimInstance -ClassName Win32_NetworkAdapter |
Where-Object {$_.PhysicalAdapter -eq $true} |
Select-Object Name, MACAddress, Speed
5.6.3 System Information Report Script
# Comprehensive system information report
$report = @()
# Computer Info
$computerInfo = Get-ComputerInfo
$report += "=== COMPUTER INFORMATION ==="
$report += "Computer Name: $($computerInfo.CsName)"
$report += "OS: $($computerInfo.OsName)"
$report += "Version: $($computerInfo.OsVersion)"
$report += "Architecture: $($computerInfo.OsArchitecture)"
$report += ""
# CPU Info
$cpu = Get-CimInstance -ClassName Win32_Processor
$report += "=== PROCESSOR ==="
$report += "Name: $($cpu.Name)"
$report += "Cores: $($cpu.NumberOfCores)"
$report += "Logical Processors: $($cpu.NumberOfLogicalProcessors)"
$report += ""
# Memory Info
$totalMemoryGB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
$report += "=== MEMORY ==="
$report += "Total Physical Memory: $totalMemoryGB GB"
$report += ""
# Disk Info
$report += "=== DISK SPACE ==="
Get-CimInstance -ClassName Win32_LogicalDisk |
Where-Object {$_.DriveType -eq 3} |
ForEach-Object {
$report += "Drive $($_.DeviceID): $([math]::Round($_.FreeSpace/1GB,2)) GB free of $([math]::Round($_.Size/1GB,2)) GB"
}
# Output report
$report | Out-File "C:\Temp\SystemReport.txt"
$report | ForEach-Object { Write-Host $_ }
5.7 Network Commands
PowerShell provides powerful networking cmdlets for testing connectivity and gathering network information.
5.7.1 Testing Network Connectivity
# Ping a host (modern PowerShell way)
Test-Connection -ComputerName "google.com" -Count 4
# Quiet mode (returns true/false)
Test-Connection -ComputerName "google.com" -Quiet
# Test multiple hosts
Test-Connection -ComputerName "google.com", "microsoft.com", "github.com" -Count 2
# Test with specific timeout
Test-Connection -ComputerName "google.com" -Count 1 -TimeoutSeconds 2
# Advanced connection test (port testing)
Test-NetConnection -ComputerName "google.com" -Port 443
# Test with detailed information
Test-NetConnection -ComputerName "google.com" -InformationLevel Detailed
# Trace route
Test-NetConnection -ComputerName "google.com" -TraceRoute
5.7.2 Network Configuration
# Get IP configuration
Get-NetIPAddress
# Get IPv4 addresses only
Get-NetIPAddress -AddressFamily IPv4
# Get IP configuration for specific adapter
Get-NetIPAddress | Where-Object {$_.InterfaceAlias -like "*Ethernet*"}
# Get network adapter information
Get-NetAdapter
# Get active network adapters
Get-NetAdapter | Where-Object {$_.Status -eq "Up"}
# Get detailed adapter information
Get-NetAdapter | Format-List *
# Get DNS client configuration
Get-DnsClientServerAddress
# Get routing table
Get-NetRoute
# Get default gateway
Get-NetRoute -DestinationPrefix "0.0.0.0/0"
5.7.3 DNS Operations
# Resolve DNS name
Resolve-DnsName "google.com"
# Get A records only
Resolve-DnsName "google.com" -Type A
# Get MX records (mail servers)
Resolve-DnsName "google.com" -Type MX
# Get all DNS records
Resolve-DnsName "google.com" -Type ALL
# Use specific DNS server
Resolve-DnsName "google.com" -Server 8.8.8.8
# Clear DNS cache
Clear-DnsClientCache
# View DNS cache
Get-DnsClientCache
5.7.4 Network Monitoring Script
# Monitor multiple hosts
$hosts = @("google.com", "microsoft.com", "github.com")
$results = @()
foreach ($host in $hosts) {
$ping = Test-Connection -ComputerName $host -Count 1 -Quiet
$results += [PSCustomObject]@{
Host = $host
Status = if ($ping) { "Online" } else { "Offline" }
Timestamp = Get-Date
}
}
$results | Format-Table -AutoSize
# Continuous monitoring with alerts
while ($true) {
$result = Test-Connection -ComputerName "google.com" -Count 1 -Quiet
if (-not $result) {
Write-Warning "Host google.com is unreachable at $(Get-Date)"
}
Start-Sleep -Seconds 60
}
5.8 User and Security Management
Managing local users and security settings is a common administrative task.
5.8.1 Local User Management
# Get all local users
Get-LocalUser
# Get specific user
Get-LocalUser -Name "Administrator"
# Get enabled users
Get-LocalUser | Where-Object {$_.Enabled -eq $true}
# Create a new local user
$password = ConvertTo-SecureString "P@ssw0rd123" -AsPlainText -Force
New-LocalUser -Name "TestUser" -Password $password -FullName "Test User" -Description "Test account"
# Disable a user
Disable-LocalUser -Name "TestUser"
# Enable a user
Enable-LocalUser -Name "TestUser"
# Remove a user
Remove-LocalUser -Name "TestUser"
# Change user password
$newPassword = ConvertTo-SecureString "NewP@ssw0rd123" -AsPlainText -Force
Set-LocalUser -Name "TestUser" -Password $newPassword
# Get local groups
Get-LocalGroup
# Get members of a group
Get-LocalGroupMember -Group "Administrators"
# Add user to group
Add-LocalGroupMember -Group "Administrators" -Member "TestUser"
# Remove user from group
Remove-LocalGroupMember -Group "Administrators" -Member "TestUser"
5.8.2 File and Folder Permissions
# Get ACL (Access Control List) for a file or folder
Get-Acl "C:\Temp"
# Get detailed ACL information
Get-Acl "C:\Temp" | Format-List
# Get ACL and display access rules
(Get-Acl "C:\Temp").Access | Format-Table IdentityReference, FileSystemRights, AccessControlType
# Set ACL (copy permissions from one folder to another)
$acl = Get-Acl "C:\Temp"
Set-Acl "C:\Temp2" -AclObject $acl
# Add permission to a folder
$acl = Get-Acl "C:\Temp"
$permission = "DOMAIN\User", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.SetAccessRule($accessRule)
Set-Acl "C:\Temp" -AclObject $acl
5.8.3 Checking Administrator Rights
# Check if running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($isAdmin) {
Write-Host "Running as Administrator" -ForegroundColor Green
} else {
Write-Host "NOT running as Administrator" -ForegroundColor Red
}
# Get current user
$env:USERNAME
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
5.9 Date, Time, and Scheduling
5.9.1 Working with Dates and Times
# Get current date and time
Get-Date
# Format date
Get-Date -Format "yyyy-MM-dd"
Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Get-Date -Format "dddd, MMMM dd, yyyy"
# Get specific date components
$date = Get-Date
$date.Year
$date.Month
$date.Day
$date.Hour
$date.Minute
$date.DayOfWeek
# Date arithmetic
$today = Get-Date
$yesterday = $today.AddDays(-1)
$nextWeek = $today.AddDays(7)
$lastMonth = $today.AddMonths(-1)
$nextYear = $today.AddYears(1)
# Calculate date difference
$startDate = Get-Date "2024-01-01"
$endDate = Get-Date
$difference = $endDate - $startDate
Write-Host "Days since January 1, 2024: $($difference.Days)"
# Get files modified in last 7 days
Get-ChildItem "C:\Temp" | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-7)}
5.9.2 Scheduled Tasks
# Get all scheduled tasks
Get-ScheduledTask
# Get specific task
Get-ScheduledTask -TaskName "MyTask"
# Get running tasks
Get-ScheduledTask | Where-Object {$_.State -eq "Running"}
# Get task information
Get-ScheduledTaskInfo -TaskName "MyTask"
# Create a new scheduled task
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\MyScript.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 9am
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "MyDailyTask" -Description "Runs daily at 9 AM"
# Start a task manually
Start-ScheduledTask -TaskName "MyTask"
# Stop a running task
Stop-ScheduledTask -TaskName "MyTask"
# Disable a task
Disable-ScheduledTask -TaskName "MyTask"
# Enable a task
Enable-ScheduledTask -TaskName "MyTask"
# Remove a task
Unregister-ScheduledTask -TaskName "MyTask" -Confirm:$false
5.10 Event Log Management
Event logs are crucial for troubleshooting and monitoring system health.
5.10.1 Reading Event Logs
# Get available event logs
Get-EventLog -List
# Get recent entries from System log
Get-EventLog -LogName System -Newest 10
# Get recent entries from Application log
Get-EventLog -LogName Application -Newest 10
# Get error entries only
Get-EventLog -LogName System -EntryType Error -Newest 20
# Get events from specific source
Get-EventLog -LogName Application -Source "Application Error" -Newest 10
# Get events after specific date
Get-EventLog -LogName System -After (Get-Date).AddDays(-7)
# Modern way using Get-WinEvent (more powerful)
Get-WinEvent -LogName System -MaxEvents 10
# Filter by event ID
Get-WinEvent -FilterHashtable @{LogName='System'; ID=1074}
# Get events from last 24 hours
Get-WinEvent -FilterHashtable @{
LogName='System'
Level=2 # Error
StartTime=(Get-Date).AddDays(-1)
}
# Search event message content
Get-WinEvent -LogName System -MaxEvents 100 |
Where-Object {$_.Message -like "*disk*"}
5.10.2 Event Log Analysis Script
# Analyze system errors from last 7 days
$startDate = (Get-Date).AddDays(-7)
$errors = Get-WinEvent -FilterHashtable @{
LogName='System'
Level=2 # Error
StartTime=$startDate
}
# Group by source
$errorsBySource = $errors | Group-Object ProviderName |
Sort-Object Count -Descending |
Select-Object Count, Name
Write-Host "Error Summary (Last 7 Days):" -ForegroundColor Yellow
$errorsBySource | Format-Table -AutoSize
# Get critical events
$critical = Get-WinEvent -FilterHashtable @{
LogName='System'
Level=1 # Critical
StartTime=$startDate
}
if ($critical) {
Write-Host "`nCritical Events Found: $($critical.Count)" -ForegroundColor Red
$critical | Select-Object TimeCreated, ProviderName, Message | Format-List
}
5.11 Hands-On Exercises
Practice what you've learned with these real-world scenarios.
Exercise 1: System Information Report
Task: Create a script that generates a comprehensive system information report including:
- Computer name and OS version
- CPU and memory information
- Disk space for all drives
- Top 5 processes by memory usage
- Network adapter information
Save the report to a text file with today's date in the filename.
Show Solution
# System Information Report Script
$reportDate = Get-Date -Format "yyyy-MM-dd"
$reportFile = "C:\Temp\SystemReport_$reportDate.txt"
$report = @()
$report += "=" * 60
$report += "SYSTEM INFORMATION REPORT"
$report += "Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$report += "=" * 60
$report += ""
# Computer and OS Info
$computerInfo = Get-ComputerInfo
$report += "COMPUTER INFORMATION"
$report += "-" * 60
$report += "Computer Name: $($computerInfo.CsName)"
$report += "OS: $($computerInfo.OsName)"
$report += "Version: $($computerInfo.OsVersion)"
$report += "Architecture: $($computerInfo.OsArchitecture)"
$report += ""
# CPU Info
$cpu = Get-CimInstance -ClassName Win32_Processor
$report += "PROCESSOR"
$report += "-" * 60
$report += "Name: $($cpu.Name)"
$report += "Cores: $($cpu.NumberOfCores)"
$report += "Logical Processors: $($cpu.NumberOfLogicalProcessors)"
$report += "Max Clock Speed: $($cpu.MaxClockSpeed) MHz"
$report += ""
# Memory Info
$totalMemoryGB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 2)
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedMemoryGB = $totalMemoryGB - $freeMemoryGB
$report += "MEMORY"
$report += "-" * 60
$report += "Total: $totalMemoryGB GB"
$report += "Used: $usedMemoryGB GB"
$report += "Free: $freeMemoryGB GB"
$report += ""
# Disk Info
$report += "DISK SPACE"
$report += "-" * 60
Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object {$_.DriveType -eq 3} | ForEach-Object {
$sizeGB = [math]::Round($_.Size / 1GB, 2)
$freeGB = [math]::Round($_.FreeSpace / 1GB, 2)
$usedGB = $sizeGB - $freeGB
$percentFree = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2)
$report += "Drive $($_.DeviceID)"
$report += " Total: $sizeGB GB"
$report += " Used: $usedGB GB"
$report += " Free: $freeGB GB ($percentFree%)"
}
$report += ""
# Top 5 Processes by Memory
$report += "TOP 5 PROCESSES BY MEMORY USAGE"
$report += "-" * 60
$topProcesses = Get-Process | Sort-Object WS -Descending | Select-Object -First 5
foreach ($proc in $topProcesses) {
$memoryMB = [math]::Round($proc.WS / 1MB, 2)
$report += "$($proc.Name): $memoryMB MB"
}
$report += ""
# Network Adapters
$report += "NETWORK ADAPTERS"
$report += "-" * 60
Get-NetAdapter | Where-Object {$_.Status -eq "Up"} | ForEach-Object {
$report += "Name: $($_.Name)"
$report += " Status: $($_.Status)"
$report += " MAC Address: $($_.MacAddress)"
$report += " Link Speed: $($_.LinkSpeed)"
}
# Save report
$report | Out-File -FilePath $reportFile
Write-Host "Report saved to: $reportFile" -ForegroundColor Green
# Display report
$report | ForEach-Object { Write-Host $_ }
Exercise 2: Process Monitor
Task: Create a script that monitors processes and alerts when any process uses more than 500MB of memory.
Show Solution
# Process Monitor Script
$memoryThresholdMB = 500
$memoryThresholdBytes = $memoryThresholdMB * 1MB
Write-Host "Monitoring processes for memory usage over $memoryThresholdMB MB..." -ForegroundColor Yellow
Write-Host "Press Ctrl+C to stop`n"
while ($true) {
$highMemoryProcesses = Get-Process | Where-Object {$_.WS -gt $memoryThresholdBytes}
if ($highMemoryProcesses) {
foreach ($proc in $highMemoryProcesses) {
$memoryMB = [math]::Round($proc.WS / 1MB, 2)
Write-Warning "$(Get-Date -Format 'HH:mm:ss') - Process '$($proc.Name)' (ID: $($proc.Id)) is using $memoryMB MB"
}
}
Start-Sleep -Seconds 30
}
Exercise 3: Service Status Checker
Task: Create a script that checks if critical services are running and attempts to start them if they're stopped.
Show Solution
# Service Status Checker Script
$criticalServices = @(
"wuauserv", # Windows Update
"BITS", # Background Intelligent Transfer Service
"EventLog", # Windows Event Log
"Spooler" # Print Spooler
)
Write-Host "Checking critical services..." -ForegroundColor Yellow
Write-Host ""
foreach ($serviceName in $criticalServices) {
try {
$service = Get-Service -Name $serviceName -ErrorAction Stop
if ($service.Status -eq "Running") {
Write-Host "[OK] $($service.DisplayName) is running" -ForegroundColor Green
} else {
Write-Warning "[WARNING] $($service.DisplayName) is $($service.Status)"
# Attempt to start the service
Write-Host "Attempting to start $($service.DisplayName)..." -ForegroundColor Yellow
try {
Start-Service -Name $serviceName -ErrorAction Stop
Write-Host "[SUCCESS] $($service.DisplayName) started successfully" -ForegroundColor Green
} catch {
Write-Host "[ERROR] Failed to start $($service.DisplayName): $($_.Exception.Message)" -ForegroundColor Red
}
}
} catch {
Write-Host "[ERROR] Service $serviceName not found" -ForegroundColor Red
}
}
Write-Host "`nService check complete." -ForegroundColor Yellow
Exercise 4: Disk Space Alert
Task: Create a script that checks all drives and alerts if any drive has less than 10% free space.
Show Solution
# Disk Space Alert Script
$threshold = 10 # Alert if free space is below 10%
Write-Host "Checking disk space on all drives..." -ForegroundColor Yellow
Write-Host ""
$drives = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object {$_.DriveType -eq 3}
foreach ($drive in $drives) {
$sizeGB = [math]::Round($drive.Size / 1GB, 2)
$freeGB = [math]::Round($drive.FreeSpace / 1GB, 2)
$percentFree = [math]::Round(($drive.FreeSpace / $drive.Size) * 100, 2)
Write-Host "Drive $($drive.DeviceID)"
Write-Host " Total: $sizeGB GB"
Write-Host " Free: $freeGB GB ($percentFree%)"
if ($percentFree -lt $threshold) {
Write-Warning " [ALERT] Low disk space on drive $($drive.DeviceID)!"
Write-Host ""
} else {
Write-Host " [OK] Sufficient space available" -ForegroundColor Green
Write-Host ""
}
}
# Summary
$lowSpaceDrives = $drives | Where-Object {(($_.FreeSpace / $_.Size) * 100) -lt $threshold}
if ($lowSpaceDrives) {
Write-Host "WARNING: $($lowSpaceDrives.Count) drive(s) have low disk space!" -ForegroundColor Red
} else {
Write-Host "All drives have sufficient space." -ForegroundColor Green
}
Exercise 5: Network Connectivity Test
Task: Create a script that tests connectivity to multiple hosts and generates a report.
Show Solution
# Network Connectivity Test Script
$hostsToTest = @(
"google.com",
"microsoft.com",
"github.com",
"stackoverflow.com"
)
Write-Host "Testing network connectivity..." -ForegroundColor Yellow
Write-Host ""
$results = @()
foreach ($host in $hostsToTest) {
Write-Host "Testing $host..." -NoNewline
$pingResult = Test-Connection -ComputerName $host -Count 2 -Quiet
$responseTime = $null
if ($pingResult) {
$ping = Test-Connection -ComputerName $host -Count 1
$responseTime = $ping.ResponseTime
Write-Host " [OK] ($responseTime ms)" -ForegroundColor Green
$status = "Online"
} else {
Write-Host " [FAILED]" -ForegroundColor Red
$status = "Offline"
}
$results += [PSCustomObject]@{
Host = $host
Status = $status
ResponseTime = $responseTime
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
Write-Host ""
Write-Host "Connectivity Report:" -ForegroundColor Yellow
$results | Format-Table -AutoSize
# Save report
$reportFile = "C:\Temp\NetworkConnectivityReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$results | Export-Csv -Path $reportFile -NoTypeInformation
Write-Host "Report saved to: $reportFile" -ForegroundColor Green
5.12 Chapter Summary
Key Takeaways
- Get-Help is your best friend - Use it to discover and learn commands
- Verb-Noun naming - Makes PowerShell commands predictable and discoverable
- File Management - Get-ChildItem, Copy-Item, Move-Item, Remove-Item
- Process Management - Get-Process, Start-Process, Stop-Process
- Service Management - Get-Service, Start-Service, Stop-Service
- System Information - Get-ComputerInfo, Get-CimInstance for hardware details
- Network Commands - Test-Connection, Test-NetConnection, Get-NetIPAddress
- User Management - Get-LocalUser, New-LocalUser, Get-Acl
- Date/Time - Get-Date with formatting and arithmetic
- Event Logs - Get-EventLog, Get-WinEvent for troubleshooting
Essential Commands Quick Reference
| Category | Key Commands |
|---|---|
| Help | Get-Help, Get-Command, Update-Help |
| Files | Get-ChildItem, Copy-Item, Move-Item, Remove-Item, Get-Content |
| Processes | Get-Process, Start-Process, Stop-Process |
| Services | Get-Service, Start-Service, Stop-Service, Restart-Service |
| System | Get-ComputerInfo, Get-CimInstance |
| Network | Test-Connection, Test-NetConnection, Get-NetIPAddress |
| Users | Get-LocalUser, New-LocalUser, Get-Acl, Set-Acl |
| Events | Get-EventLog, Get-WinEvent |
5.13 Chapter Quiz
1. What is the correct syntax for getting help with examples for a command?
A) Get-Help Get-Process -Full
B) Get-Help Get-Process -Examples
C) Get-Process -Help
D) Help Get-Process -Detailed
2. Which command lists all files and folders in the current directory?
A) List-Items
B) Get-Files
C) Get-ChildItem
D) Show-Directory
3. How do you stop a process named "notepad"?
A) Stop-Process -Name "notepad"
B) Kill-Process "notepad"
C) End-Process -Process "notepad"
D) Terminate-Process "notepad"
4. What command tests network connectivity to a host?
A) Ping-Host
B) Test-Connection
C) Check-Network
D) Get-Connectivity
5. Which command retrieves running services only?
A) Get-Service -Running
B) Get-Service | Where-Object {$_.Status -eq "Running"}
C) Get-RunningServices
D) Show-Service -Active
6. How do you get the top 5 processes by memory usage?
A) Get-Process -Top 5 -SortBy Memory
B) Get-Process | Sort-Object WS -Descending | Select-Object -First 5
C) Get-TopProcesses -Count 5 -Memory
D) Show-Process -Memory -Limit 5
7. What property of Get-Service indicates if a service is running?
A) State
B) Status
C) Running
D) Active
8. Which command gets system uptime?
A) Get-Uptime
B) (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
C) Get-SystemInfo -Uptime
D) Show-Uptime
9. How do you read the last 10 lines of a log file?
A) Get-Content logfile.txt -Last 10
B) Get-Content logfile.txt -Tail 10
C) Read-File logfile.txt -Bottom 10
D) Both A and B
10. What does the -Recurse parameter do with Get-ChildItem?
A) Repeats the command multiple times
B) Lists files in subdirectories as well
C) Reverses the sort order
D) Refreshes the file list automatically
Chapter 6: Variables and Data Types
π Learning Objectives
By the end of this chapter, you will be able to:
- Declare and use variables in PowerShell
- Understand PowerShell's type system and automatic type conversion
- Work with strings, numbers, arrays, and hashtables
- Understand variable scope (local, script, global)
- Use special automatic variables
- Perform type casting and validation
6.1 Introduction to Variables
Variables in PowerShell are containers that store data. They always start with a dollar sign ($) and can hold any type of data.
6.1.1 Variable Naming Rules
- Must start with
$ - Can contain letters, numbers, and underscores
- Are not case-sensitive (
$nameand$Nameare the same) - Should be descriptive and follow naming conventions
# Valid variable names
$userName = "John"
$user_age = 30
$serverIP = "192.168.1.1"
$isActive = $true
$counter1 = 0
# Invalid variable names (will cause errors)
# userName = "John" # Missing $
# $user-name = "John" # Hyphens not allowed
# $1counter = 0 # Cannot start with number
6.1.2 Creating and Assigning Variables
# Simple assignment
$name = "Alice"
$age = 25
$salary = 50000.50
# Display variable values
Write-Host "Name: $name"
Write-Host "Age: $age"
Write-Host "Salary: $salary"
# Multiple assignments
$x = $y = $z = 0
# Reassigning variables
$counter = 1
$counter = 2
$counter = "Now I'm a string!" # Type can change
6.2 Data Types in PowerShell
PowerShell supports many data types. Here are the most common ones:
| Type | Description | Example |
|---|---|---|
[string] |
Text data | "Hello World" |
[int] |
32-bit integer | 42 |
[long] |
64-bit integer | 9223372036854775807 |
[double] |
Floating-point number | 3.14159 |
[decimal] |
High-precision decimal | 19.99 |
[bool] |
Boolean (true/false) | $true, $false |
[datetime] |
Date and time | Get-Date |
[array] |
Collection of items | @(1, 2, 3) |
[hashtable] |
Key-value pairs | @{Name="John"} |
6.2.1 Checking Variable Types
# Get the type of a variable
$name = "John"
$name.GetType()
# More detailed type information
$age = 30
$age.GetType().FullName # System.Int32
# Check if variable is of specific type
$value = 42
$value -is [int] # Returns True
$value -is [string] # Returns False
# Display type in readable format
$salary = 50000.50
Write-Host "Type: $($salary.GetType().Name)" # Double
6.3 Working with Strings
Strings are one of the most commonly used data types in PowerShell.
6.3.1 String Creation and Quotes
# Double quotes - Variables are expanded
$name = "Alice"
$greeting = "Hello, $name!"
Write-Host $greeting # Output: Hello, Alice!
# Single quotes - Literal string (no variable expansion)
$greeting2 = 'Hello, $name!'
Write-Host $greeting2 # Output: Hello, $name!
# Escape characters in double quotes
$path = "C:\Users\John\Documents"
$quote = "He said, \"Hello\""
# Here-strings for multi-line text
$multiLine = @"
This is line 1
This is line 2
Variables work here: $name
"@
$literalMultiLine = @'
This is literal
$name will not expand
'@
6.3.2 String Operations
# String concatenation
$firstName = "John"
$lastName = "Doe"
$fullName = $firstName + " " + $lastName
# Or using string interpolation
$fullName = "$firstName $lastName"
# String length
$text = "PowerShell"
$length = $text.Length # 10
# String methods
$text = " PowerShell Rocks! "
$text.ToUpper() # " POWERSHELL ROCKS! "
$text.ToLower() # " powershell rocks! "
$text.Trim() # "PowerShell Rocks!"
$text.TrimStart() # "PowerShell Rocks! "
$text.TrimEnd() # " PowerShell Rocks!"
# Substring
$text = "PowerShell"
$text.Substring(0, 5) # "Power"
$text.Substring(5) # "Shell"
# Replace
$text = "Hello World"
$text.Replace("World", "PowerShell") # "Hello PowerShell"
# Split
$csv = "John,Doe,30,Engineer"
$fields = $csv.Split(",") # Creates array: @("John", "Doe", "30", "Engineer")
# Contains, StartsWith, EndsWith
$text = "PowerShell"
$text.Contains("Shell") # True
$text.StartsWith("Power") # True
$text.EndsWith("Python") # False
# String formatting
$name = "Alice"
$age = 30
$formatted = "Name: {0}, Age: {1}" -f $name, $age
# Or using string interpolation
$formatted = "Name: $name, Age: $age"
6.4 Working with Numbers
6.4.1 Numeric Types and Operations
# Integer operations
$a = 10
$b = 3
$sum = $a + $b # 13
$difference = $a - $b # 7
$product = $a * $b # 30
$quotient = $a / $b # 3.33333...
$remainder = $a % $b # 1 (modulo)
# Increment and decrement
$counter = 0
$counter++ # 1
$counter += 5 # 6
$counter-- # 5
$counter -= 2 # 3
# Floating-point numbers
$pi = 3.14159
$radius = 5
$area = $pi * $radius * $radius
# Rounding
$value = 3.7
[Math]::Round($value) # 4
[Math]::Floor($value) # 3
[Math]::Ceiling($value) # 4
[Math]::Round($value, 2) # 3.70
# Other math operations
[Math]::Abs(-5) # 5
[Math]::Pow(2, 3) # 8 (2^3)
[Math]::Sqrt(16) # 4
[Math]::Max(10, 20) # 20
[Math]::Min(10, 20) # 10
# Converting between numeric types
$intValue = 42
$doubleValue = [double]$intValue
$stringValue = $intValue.ToString()
6.5 Working with Booleans
# Boolean literals
$isActive = $true
$isDeleted = $false
# Boolean from comparisons
$age = 25
$isAdult = $age -ge 18 # True
# Boolean operations
$a = $true
$b = $false
$and = $a -and $b # False
$or = $a -or $b # True
$not = -not $a # False
$xor = $a -xor $b # True
# Truthiness in PowerShell
# These evaluate to $false:
# $false, $null, 0, "", @()
# Everything else evaluates to $true
if ($null) { "True" } else { "False" } # False
if (0) { "True" } else { "False" } # False
if ("") { "True" } else { "False" } # False
if ("text") { "True" } else { "False" } # True
if (1) { "True" } else { "False" } # True
6.6 Working with Arrays
Arrays are collections that can hold multiple values.
6.6.1 Creating and Accessing Arrays
# Creating arrays
$colors = @("Red", "Green", "Blue")
$numbers = 1, 2, 3, 4, 5
$mixed = @(1, "two", 3.0, $true)
$empty = @()
# Accessing array elements (zero-based index)
$colors[0] # "Red"
$colors[1] # "Green"
$colors[-1] # "Blue" (last element)
$colors[-2] # "Green" (second to last)
# Array length
$count = $colors.Count # 3
$count = $colors.Length # 3
# Array slicing
$numbers = 1, 2, 3, 4, 5
$numbers[0..2] # 1, 2, 3
$numbers[2..4] # 3, 4, 5
$numbers[0, 2, 4] # 1, 3, 5
# Modifying arrays
$colors[0] = "Yellow" # Change first element
# Adding to arrays (creates new array)
$colors = $colors + "Purple"
$colors += "Orange"
# Array contains
$colors -contains "Red" # True or False
$colors -notcontains "Pink" # True or False
6.6.2 Array Operations
# Looping through arrays
$fruits = @("Apple", "Banana", "Cherry")
foreach ($fruit in $fruits) {
Write-Host $fruit
}
# Using ForEach-Object
$fruits | ForEach-Object {
Write-Host "I like $_"
}
# Filtering arrays
$numbers = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
$evenNumbers = $numbers | Where-Object { $_ % 2 -eq 0 }
# Result: 2, 4, 6, 8, 10
# Sorting arrays
$names = @("Charlie", "Alice", "Bob")
$sorted = $names | Sort-Object
# Result: Alice, Bob, Charlie
$numbers = 5, 2, 8, 1, 9
$sortedDesc = $numbers | Sort-Object -Descending
# Result: 9, 8, 5, 2, 1
# Selecting specific elements
$numbers = 1, 2, 3, 4, 5
$first3 = $numbers | Select-Object -First 3 # 1, 2, 3
$last2 = $numbers | Select-Object -Last 2 # 4, 5
# Joining array elements
$words = @("Power", "Shell", "Rocks")
$sentence = $words -join " " # "Power Shell Rocks"
$csv = $words -join "," # "Power,Shell,Rocks"
# Measuring arrays
$numbers = 1, 2, 3, 4, 5
$stats = $numbers | Measure-Object -Sum -Average -Maximum -Minimum
$stats.Sum # 15
$stats.Average # 3
$stats.Maximum # 5
$stats.Minimum # 1
6.7 Working with Hashtables
Hashtables (also called dictionaries or associative arrays) store key-value pairs.
6.7.1 Creating and Accessing Hashtables
# Creating hashtables
$person = @{
Name = "John Doe"
Age = 30
City = "New York"
IsEmployed = $true
}
# Accessing values
$person["Name"] # "John Doe"
$person.Name # "John Doe" (dot notation)
$person["Age"] # 30
# Adding new keys
$person["Email"] = "[email protected]"
$person.Phone = "555-1234"
# Modifying values
$person["Age"] = 31
$person.City = "Boston"
# Removing keys
$person.Remove("Phone")
# Check if key exists
$person.ContainsKey("Name") # True
$person.ContainsKey("Salary") # False
# Get all keys and values
$person.Keys # Name, Age, City, IsEmployed, Email
$person.Values # John Doe, 31, Boston, True, [email protected]
# Count of items
$person.Count # 5
6.7.2 Looping Through Hashtables
$server = @{
Name = "WEB01"
IP = "192.168.1.100"
OS = "Windows Server 2022"
RAM = "16GB"
}
# Loop through keys and values
foreach ($key in $server.Keys) {
Write-Host "$key : $($server[$key])"
}
# Using GetEnumerator()
$server.GetEnumerator() | ForEach-Object {
Write-Host "$($_.Key) = $($_.Value)"
}
# Ordered hashtables (maintains insertion order)
$orderedHash = [ordered]@{
First = 1
Second = 2
Third = 3
}
# Nested hashtables
$company = @{
Name = "TechCorp"
Employees = @{
CEO = "Alice"
CTO = "Bob"
CFO = "Charlie"
}
Revenue = 1000000
}
# Accessing nested values
$company.Employees.CEO # "Alice"
$company["Employees"]["CTO"] # "Bob"
6.8 Type Casting and Conversion
PowerShell can automatically convert between types, but you can also explicitly cast types.
# Explicit type casting
[int]$age = "30" # String to Integer
[string]$number = 42 # Integer to String
[double]$price = "19.99" # String to Double
[bool]$flag = 1 # Integer to Boolean
# Type declaration (enforces type)
[int]$count = 10
$count = "text" # ERROR! Cannot convert string to int
# Converting strings to numbers
$text = "123"
$number = [int]$text # 123
$number = [convert]::ToInt32($text)
# Converting to DateTime
$dateString = "2026-06-05"
$date = [datetime]$dateString
$date = Get-Date $dateString
# Converting arrays
$stringArray = "1", "2", "3"
$intArray = $stringArray | ForEach-Object { [int]$_ }
# Safe conversion with try/catch
$input = "abc"
try {
$number = [int]$input
} catch {
Write-Host "Cannot convert '$input' to integer"
$number = 0
}
# Checking if conversion is possible
$value = "123"
$result = $value -as [int]
if ($result -ne $null) {
Write-Host "Conversion successful: $result"
}
6.9 Variable Scope
PowerShell has different scopes that determine where variables are accessible.
| Scope | Description | Syntax |
|---|---|---|
| Local | Current scope only (default) | $variable or $local:variable |
| Script | Available throughout the script | $script:variable |
| Global | Available everywhere in the session | $global:variable |
| Private | Not inherited by child scopes | $private:variable |
# Global scope - accessible everywhere
$global:companyName = "TechCorp"
# Script scope - accessible throughout the script
$script:version = "1.0"
# Local scope (default)
$localVar = "I'm local"
# Function demonstrating scope
function Test-Scope {
# This creates a local variable
$localVar = "Function local"
# Access global variable
Write-Host "Global: $global:companyName"
# Access script variable
Write-Host "Script: $script:version"
# Access local variable
Write-Host "Local: $localVar"
# Modify global variable
$global:companyName = "NewCorp"
}
Test-Scope
# After function execution
Write-Host $companyName # "NewCorp" (global was modified)
Write-Host $localVar # "I'm local" (function's local didn't affect this)
# Private scope example
$private:secret = "Hidden"
function Try-AccessSecret {
Write-Host $secret # Will be empty/null - private not inherited
}
# Using scope in scripts
# Script1.ps1
$script:config = @{
Server = "localhost"
Port = 8080
}
function Get-Config {
return $script:config
}
function Set-ConfigValue {
param($key, $value)
$script:config[$key] = $value
}
6.10 Special Automatic Variables
PowerShell provides many automatic variables that contain useful information.
| Variable | Description | Example Value |
|---|---|---|
$_ |
Current pipeline object | Varies |
$PSVersionTable |
PowerShell version information | Hashtable with version details |
$HOME |
User's home directory | C:\Users\Username |
$PWD |
Current directory | C:\Scripts |
$PSScriptRoot |
Directory of running script | C:\Scripts |
$args |
Array of function arguments | @("arg1", "arg2") |
$error |
Array of recent errors | Error objects |
$null |
Represents nothing/empty | null |
$true / $false |
Boolean values | True/False |
$PID |
Current process ID | 12345 |
# $_ - Current object in pipeline
1, 2, 3, 4, 5 | ForEach-Object {
Write-Host "Number: $_"
}
Get-Process | Where-Object { $_.CPU -gt 10 }
# $PSVersionTable - PowerShell version
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)"
Write-Host "Edition: $($PSVersionTable.PSEdition)"
# $HOME - Home directory
Write-Host "Home: $HOME"
$documentsPath = "$HOME\Documents"
# $PWD - Current directory
Write-Host "Current directory: $PWD"
# $PSScriptRoot - Script directory (useful in scripts)
# Script: C:\Scripts\MyScript.ps1
$configFile = "$PSScriptRoot\config.json"
$logFile = "$PSScriptRoot\logs\app.log"
# $args - Function arguments
function Show-Arguments {
Write-Host "Number of arguments: $($args.Count)"
foreach ($arg in $args) {
Write-Host "Argument: $arg"
}
}
Show-Arguments "one" "two" "three"
# $error - Recent errors
Get-Process "NonExistentProcess" -ErrorAction SilentlyContinue
if ($error.Count -gt 0) {
Write-Host "Last error: $($error[0].Exception.Message)"
}
# Clear error list
$error.Clear()
# $null - Testing for empty values
$value = $null
if ($value -eq $null) {
Write-Host "Value is null"
}
# $PID - Current process ID
Write-Host "PowerShell PID: $PID"
# $Host - Host application information
Write-Host "Host Name: $($Host.Name)"
Write-Host "Host Version: $($Host.Version)"
6.11 Best Practices for Variables
β Variable Best Practices
- Use descriptive names:
$userNameinstead of$u - Use camelCase or PascalCase:
$serverNameor$ServerName - Declare types when needed:
[int]$count = 0for type safety - Initialize variables: Don't use uninitialized variables
- Use appropriate scope: Keep variables as local as possible
- Use $null checks: Test for null before using variables
- Clear sensitive data:
$password = $nullafter use - Use constants for fixed values:
Set-Variable -Name MAX_RETRIES -Value 3 -Option Constant
β οΈ Common Pitfalls
- Uninitialized variables: Always initialize before use
- Type confusion: Be aware of automatic type conversion
- Scope issues: Understand where variables are accessible
- Array immutability: Adding to arrays creates new arrays (use ArrayList for better performance)
- String vs Integer:
"10" + "20"= "1020" (string), not 30
6.12 Hands-On Exercises
Exercise 1: Variable Manipulation
Task: Create variables for a user profile and perform various operations.
- Create variables for: firstName, lastName, age, email, isActive
- Combine firstName and lastName into fullName
- Calculate birth year from age
- Create a hashtable with all user information
- Display the information in a formatted way
Show Solution
# Create variables
$firstName = "John"
$lastName = "Doe"
$age = 30
$email = "[email protected]"
$isActive = $true
# Combine names
$fullName = "$firstName $lastName"
# Calculate birth year
$currentYear = (Get-Date).Year
$birthYear = $currentYear - $age
# Create hashtable
$userProfile = @{
FullName = $fullName
Age = $age
BirthYear = $birthYear
Email = $email
IsActive = $isActive
}
# Display information
Write-Host "=" * 40
Write-Host "USER PROFILE"
Write-Host "=" * 40
foreach ($key in $userProfile.Keys) {
Write-Host "$key : $($userProfile[$key])"
}
Write-Host "=" * 40
# Alternative: Using custom object
$user = [PSCustomObject]@{
FullName = $fullName
Age = $age
BirthYear = $birthYear
Email = $email
IsActive = $isActive
}
$user | Format-Table -AutoSize
Exercise 2: Array Operations
Task: Work with an array of numbers and perform various operations.
- Create an array of 10 random numbers between 1 and 100
- Find the sum, average, minimum, and maximum
- Filter numbers greater than 50
- Sort the array in descending order
- Display the results
Show Solution
# Create array of random numbers
$numbers = @()
for ($i = 0; $i -lt 10; $i++) {
$numbers += Get-Random -Minimum 1 -Maximum 101
}
Write-Host "Original array: $($numbers -join ', ')"
Write-Host ""
# Calculate statistics
$stats = $numbers | Measure-Object -Sum -Average -Maximum -Minimum
Write-Host "Statistics:"
Write-Host " Sum: $($stats.Sum)"
Write-Host " Average: $([math]::Round($stats.Average, 2))"
Write-Host " Maximum: $($stats.Maximum)"
Write-Host " Minimum: $($stats.Minimum)"
Write-Host ""
# Filter numbers > 50
$filtered = $numbers | Where-Object { $_ -gt 50 }
Write-Host "Numbers greater than 50: $($filtered -join ', ')"
Write-Host "Count: $($filtered.Count)"
Write-Host ""
# Sort descending
$sorted = $numbers | Sort-Object -Descending
Write-Host "Sorted (descending): $($sorted -join ', ')"
Exercise 3: String Manipulation
Task: Process a log file entry and extract information.
- Given a log entry: "2026-06-05 14:30:15 ERROR UserService Failed to authenticate user: [email protected]"
- Extract: date, time, level, service, message, email
- Store in a hashtable
- Display in a formatted way
Show Solution
# Log entry
$logEntry = "2026-06-05 14:30:15 ERROR UserService Failed to authenticate user: [email protected]"
# Split the log entry
$parts = $logEntry.Split(" ")
# Extract components
$date = $parts[0]
$time = $parts[1]
$level = $parts[2]
$service = $parts[3]
$message = $parts[4..($parts.Length - 1)] -join " "
# Extract email using regex
$email = if ($message -match '([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})') {
$matches[1]
} else {
"N/A"
}
# Create hashtable
$logData = @{
Date = $date
Time = $time
Level = $level
Service = $service
Message = $message
Email = $email
}
# Display
Write-Host "LOG ENTRY ANALYSIS"
Write-Host "=" * 50
foreach ($key in $logData.Keys) {
Write-Host "$key : $($logData[$key])"
}
Write-Host "=" * 50
# Alternative: Using regex for more robust parsing
if ($logEntry -match '^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (\w+) (.+)$') {
$parsedLog = [PSCustomObject]@{
Date = $matches[1]
Time = $matches[2]
Level = $matches[3]
Service = $matches[4]
Message = $matches[5]
}
$parsedLog | Format-List
}
6.13 Chapter Summary
Key Takeaways
- Variables start with $ and are dynamically typed
- Common data types: string, int, double, bool, array, hashtable
- Strings: Use double quotes for variable expansion, single quotes for literals
- Arrays: Collections indexed from 0, can contain mixed types
- Hashtables: Key-value pairs, accessed via keys or dot notation
- Type casting: Use [type] syntax to convert between types
- Scope: Local (default), Script, Global, Private
- Automatic variables: $_, $PSVersionTable, $HOME, $PWD, etc.
- Best practices: Descriptive names, appropriate scope, type safety
6.14 Chapter Quiz
1. What is the difference between single and double quotes in PowerShell?
A) No difference
B) Single quotes expand variables, double quotes don't
C) Double quotes expand variables, single quotes treat them as literals
D) Single quotes are faster
2. How do you access the third element of an array?
A) $array[3]
B) $array[2]
C) $array(3)
D) $array.3
3. What does the $_ variable represent?
A) The last error
B) The current pipeline object
C) The script root directory
D) The current user
4. How do you create an empty hashtable?
A) $hash = {}
B) $hash = @{}
C) $hash = []
D) $hash = @()
5. What is the result of: "10" + "20"?
A) 30
B) "1020"
C) Error
D) "30"
6. How do you declare a variable that can only hold integers?
A) $int:number = 10
B) [int]$number = 10
C) $number = [int]10
D) int $number = 10
7. What scope should you use for a variable that needs to be accessible throughout a script file?
A) Local
B) Global
C) Script
D) Private
8. How do you get the number of elements in an array?
A) $array.Size
B) $array.Length or $array.Count
C) $array.Elements
D) Count($array)
9. What does [Math]::Round(3.7) return?
A) 3
B) 3.7
C) 4
D) 4.0
10. How do you access a hashtable value with the key "Name"?
A) $hash.Name or $hash["Name"]
B) $hash->Name
C) $hash("Name")
D) $hash::Name
Chapter 7: Operators and Expressions
π Learning Objectives
By the end of this chapter, you will be able to:
- Use arithmetic operators for mathematical calculations
- Apply comparison operators to evaluate conditions
- Combine conditions using logical operators
- Manipulate strings with string operators
- Check and convert types using type operators
- Understand operator precedence and associativity
- Write complex expressions efficiently
7.1 Introduction to Operators
Operators are symbols that tell PowerShell to perform specific operations on values (operands). PowerShell supports a rich set of operators that make it a powerful scripting language.
== or !=, PowerShell uses descriptive operators like -eq (equals) and -ne (not equals). This makes code more readable.
7.2 Arithmetic Operators
Arithmetic operators perform mathematical operations on numeric values.
7.2.1 Basic Arithmetic Operators
| Operator | Description | Example | Result |
|---|---|---|---|
+ |
Addition | 5 + 3 |
8 |
- |
Subtraction | 5 - 3 |
2 |
* |
Multiplication | 5 * 3 |
15 |
/ |
Division | 10 / 2 |
5 |
% |
Modulus (remainder) | 10 % 3 |
1 |
7.2.2 Arithmetic Examples
# Addition
$sum = 10 + 5
Write-Host "Sum: $sum" # Output: 15
# Subtraction
$difference = 10 - 5
Write-Host "Difference: $difference" # Output: 5
# Multiplication
$product = 10 * 5
Write-Host "Product: $product" # Output: 50
# Division
$quotient = 10 / 5
Write-Host "Quotient: $quotient" # Output: 2
# Modulus (remainder)
$remainder = 10 % 3
Write-Host "Remainder: $remainder" # Output: 1
# Combining operations
$result = (10 + 5) * 2 - 3
Write-Host "Result: $result" # Output: 27
7.2.3 Special Arithmetic Operations
# Power (using .NET Math class)
$power = [Math]::Pow(2, 8)
Write-Host "2^8 = $power" # Output: 256
# Square root
$sqrt = [Math]::Sqrt(16)
Write-Host "Square root of 16: $sqrt" # Output: 4
# Rounding
$rounded = [Math]::Round(3.7)
Write-Host "Rounded: $rounded" # Output: 4
# Ceiling and Floor
$ceiling = [Math]::Ceiling(3.2)
$floor = [Math]::Floor(3.8)
Write-Host "Ceiling: $ceiling, Floor: $floor" # Output: 4, 3
# Absolute value
$abs = [Math]::Abs(-10)
Write-Host "Absolute: $abs" # Output: 10
7.3 Assignment Operators
Assignment operators assign values to variables. PowerShell includes compound assignment operators that combine arithmetic with assignment.
| Operator | Description | Example | Equivalent To |
|---|---|---|---|
= |
Simple assignment | $x = 10 |
- |
+= |
Add and assign | $x += 5 |
$x = $x + 5 |
-= |
Subtract and assign | $x -= 5 |
$x = $x - 5 |
*= |
Multiply and assign | $x *= 5 |
$x = $x * 5 |
/= |
Divide and assign | $x /= 5 |
$x = $x / 5 |
%= |
Modulus and assign | $x %= 5 |
$x = $x % 5 |
# Simple assignment
$counter = 0
Write-Host "Initial: $counter" # Output: 0
# Increment using +=
$counter += 1
Write-Host "After +=: $counter" # Output: 1
# Add 5
$counter += 5
Write-Host "After += 5: $counter" # Output: 6
# Multiply by 2
$counter *= 2
Write-Host "After *=: $counter" # Output: 12
# Divide by 3
$counter /= 3
Write-Host "After /=: $counter" # Output: 4
# Subtract 2
$counter -= 2
Write-Host "After -=: $counter" # Output: 2
7.4 Comparison Operators
Comparison operators compare values and return $true or $false. PowerShell's comparison operators are case-insensitive by default.
7.4.1 Basic Comparison Operators
| Operator | Description | Example | Result |
|---|---|---|---|
-eq |
Equal to | 5 -eq 5 |
$true |
-ne |
Not equal to | 5 -ne 3 |
$true |
-gt |
Greater than | 5 -gt 3 |
$true |
-ge |
Greater than or equal | 5 -ge 5 |
$true |
-lt |
Less than | 3 -lt 5 |
$true |
-le |
Less than or equal | 3 -le 5 |
$true |
# Numeric comparisons
$a = 10
$b = 20
Write-Host "10 -eq 20: $($a -eq $b)" # False
Write-Host "10 -ne 20: $($a -ne $b)" # True
Write-Host "10 -lt 20: $($a -lt $b)" # True
Write-Host "10 -gt 20: $($a -gt $b)" # False
Write-Host "10 -le 10: $($a -le 10)" # True
Write-Host "10 -ge 10: $($a -ge 10)" # True
# String comparisons (case-insensitive by default)
$name1 = "PowerShell"
$name2 = "powershell"
Write-Host "'PowerShell' -eq 'powershell': $($name1 -eq $name2)" # True
# Case-sensitive comparison
Write-Host "'PowerShell' -ceq 'powershell': $($name1 -ceq $name2)" # False
7.4.2 Case-Sensitive Comparison Operators
| Operator | Description | Example |
|---|---|---|
-ceq |
Case-sensitive equal | "A" -ceq "a" β $false |
-cne |
Case-sensitive not equal | "A" -cne "a" β $true |
-cgt |
Case-sensitive greater than | "B" -cgt "a" β $false |
-clt |
Case-sensitive less than | "a" -clt "B" β $false |
7.5 Logical Operators
Logical operators combine multiple conditions and return boolean results.
| Operator | Description | Example | Result |
|---|---|---|---|
-and |
Logical AND | $true -and $true |
$true |
-or |
Logical OR | $true -or $false |
$true |
-not or ! |
Logical NOT | -not $true |
$false |
-xor |
Exclusive OR | $true -xor $false |
$true |
# AND operator - both conditions must be true
$age = 25
$hasLicense = $true
if ($age -ge 18 -and $hasLicense) {
Write-Host "Can drive"
} else {
Write-Host "Cannot drive"
}
# OR operator - at least one condition must be true
$isWeekend = $false
$isHoliday = $true
if ($isWeekend -or $isHoliday) {
Write-Host "Day off!"
} else {
Write-Host "Work day"
}
# NOT operator - inverts the boolean value
$isRaining = $false
if (-not $isRaining) {
Write-Host "Go for a walk"
}
# XOR operator - true if conditions are different
$option1 = $true
$option2 = $false
if ($option1 -xor $option2) {
Write-Host "Exactly one option is selected"
}
# Complex logical expression
$temperature = 25
$isSunny = $true
$hasUmbrella = $false
if (($temperature -gt 20 -and $isSunny) -or $hasUmbrella) {
Write-Host "Good weather for outdoor activity"
}
7.5.1 Truth Table for Logical Operators
| A | B | A -and B | A -or B | A -xor B | -not A |
|---|---|---|---|---|---|
| $true | $true | $true | $true | $false | $false |
| $true | $false | $false | $true | $true | $false |
| $false | $true | $false | $true | $true | $true |
| $false | $false | $false | $false | $false | $true |
7.6 String Operators
String operators perform operations specifically designed for text manipulation.
| Operator | Description | Example |
|---|---|---|
-like |
Wildcard pattern matching | "PowerShell" -like "Power*" β $true |
-notlike |
Wildcard pattern not matching | "PowerShell" -notlike "Bash*" β $true |
-match |
Regular expression matching | "abc123" -match "\d+" β $true |
-notmatch |
Regular expression not matching | "abc" -notmatch "\d+" β $true |
-replace |
Replace text using regex | "Hello" -replace "H", "J" β "Jello" |
-split |
Split string into array | "a,b,c" -split "," β @("a","b","c") |
-join |
Join array into string | @("a","b","c") -join "," β "a,b,c" |
-contains |
Check if collection contains item | @(1,2,3) -contains 2 β $true |
-in |
Check if item is in collection | 2 -in @(1,2,3) β $true |
7.6.1 Wildcard Pattern Matching (-like)
# * matches zero or more characters
$filename = "report.txt"
Write-Host "$filename -like '*.txt': $($filename -like '*.txt')" # True
Write-Host "$filename -like 'report*': $($filename -like 'report*')" # True
Write-Host "$filename -like '*port*': $($filename -like '*port*')" # True
# ? matches exactly one character
$code = "ABC123"
Write-Host "$code -like 'ABC???': $($code -like 'ABC???')" # True
Write-Host "$code -like 'ABC??': $($code -like 'ABC??')" # False
# [abc] matches any character in the set
$letter = "b"
Write-Host "$letter -like '[abc]': $($letter -like '[abc]')" # True
# Filter files
$files = @("report.txt", "data.csv", "notes.txt", "image.png")
$textFiles = $files | Where-Object { $_ -like "*.txt" }
Write-Host "Text files: $($textFiles -join ', ')"
7.6.2 Regular Expression Matching (-match)
# Match digits
$text = "Order number: 12345"
if ($text -match "\d+") {
Write-Host "Contains numbers: $($matches[0])" # Output: 12345
}
# Match email pattern
$email = "[email protected]"
$emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if ($email -match $emailPattern) {
Write-Host "Valid email format"
}
# Extract date from string
$log = "Error occurred on 2026-06-05 at 15:30"
if ($log -match "(\d{4}-\d{2}-\d{2})") {
$date = $matches[1]
Write-Host "Date extracted: $date" # Output: 2026-06-05
}
# Match IP address
$ip = "192.168.1.1"
$ipPattern = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
if ($ip -match $ipPattern) {
Write-Host "Valid IP format"
}
7.6.3 String Replacement and Splitting
# Replace text
$text = "Hello World"
$newText = $text -replace "World", "PowerShell"
Write-Host $newText # Output: Hello PowerShell
# Replace using regex
$phone = "123-456-7890"
$cleanPhone = $phone -replace "-", ""
Write-Host $cleanPhone # Output: 1234567890
# Replace multiple spaces with single space
$messyText = "Too many spaces"
$cleanText = $messyText -replace "\s+", " "
Write-Host $cleanText # Output: Too many spaces
# Split string
$csv = "John,Doe,30,Engineer"
$fields = $csv -split ","
Write-Host "First name: $($fields[0])"
Write-Host "Last name: $($fields[1])"
Write-Host "Age: $($fields[2])"
Write-Host "Job: $($fields[3])"
# Split by multiple delimiters
$data = "apple;banana,orange;grape"
$fruits = $data -split "[;,]"
Write-Host "Fruits: $($fruits -join ', ')"
# Join array elements
$words = @("PowerShell", "is", "awesome")
$sentence = $words -join " "
Write-Host $sentence # Output: PowerShell is awesome
7.7 Type Operators
Type operators check or convert the type of objects.
| Operator | Description | Example |
|---|---|---|
-is |
Check if object is of specified type | $x -is [int] |
-isnot |
Check if object is not of specified type | $x -isnot [string] |
-as |
Convert object to specified type | $x -as [int] |
# Check variable types
$number = 42
$text = "Hello"
$array = @(1, 2, 3)
Write-Host "`$number is [int]: $($number -is [int])" # True
Write-Host "`$text is [string]: $($text -is [string])" # True
Write-Host "`$array is [array]: $($array -is [array])" # True
Write-Host "`$number is [string]: $($number -is [string])" # False
# Type conversion using -as
$stringNumber = "123"
$convertedNumber = $stringNumber -as [int]
Write-Host "Type: $($convertedNumber.GetType().Name)" # Int32
Write-Host "Value: $convertedNumber" # 123
# Safe conversion (returns $null if fails)
$invalidNumber = "abc"
$result = $invalidNumber -as [int]
if ($null -eq $result) {
Write-Host "Conversion failed"
} else {
Write-Host "Converted: $result"
}
# Check for specific .NET types
$date = Get-Date
Write-Host "Is DateTime: $($date -is [DateTime])" # True
# Check inheritance
$process = Get-Process | Select-Object -First 1
Write-Host "Is Object: $($process -is [Object])" # True
7.8 Containment Operators
Containment operators check if a value exists in a collection.
# -contains checks if collection contains an item
$fruits = @("apple", "banana", "orange", "grape")
if ($fruits -contains "banana") {
Write-Host "We have bananas!"
}
if ($fruits -notcontains "mango") {
Write-Host "No mangos available"
}
# -in checks if item is in collection (reverse of -contains)
$searchFruit = "apple"
if ($searchFruit -in $fruits) {
Write-Host "$searchFruit is in the list"
}
# Case-sensitive versions
$names = @("John", "jane", "Bob")
Write-Host "Contains 'jane': $($names -contains 'jane')" # True
Write-Host "Contains 'Jane': $($names -ccontains 'Jane')" # False (case-sensitive)
# Practical example: Check if service is in critical list
$criticalServices = @("wuauserv", "BITS", "EventLog")
$serviceName = "BITS"
if ($serviceName -in $criticalServices) {
Write-Host "$serviceName is a critical service"
}
7.9 Operator Precedence
When multiple operators appear in an expression, PowerShell evaluates them in a specific order.
| Precedence | Operator | Description |
|---|---|---|
| 1 (Highest) | ( ) |
Parentheses (grouping) |
| 2 | -not, ! |
Logical NOT |
| 3 | *, /, % |
Multiplication, Division, Modulus |
| 4 | +, - |
Addition, Subtraction |
| 5 | -eq, -ne, -gt, -lt, -ge, -le |
Comparison operators |
| 6 | -and |
Logical AND |
| 7 | -or, -xor |
Logical OR, XOR |
| 8 (Lowest) | =, +=, -=, *=, /=, %= |
Assignment operators |
# Without parentheses - follows precedence rules
$result1 = 2 + 3 * 4
Write-Host "2 + 3 * 4 = $result1" # Output: 14 (not 20)
# With parentheses - changes evaluation order
$result2 = (2 + 3) * 4
Write-Host "(2 + 3) * 4 = $result2" # Output: 20
# Complex expression
$result3 = 10 + 5 * 2 - 3
Write-Host "10 + 5 * 2 - 3 = $result3" # Output: 17
# Logical operators precedence
$a = $true
$b = $false
$c = $true
# AND has higher precedence than OR
$result4 = $a -or $b -and $c
Write-Host "`$true -or `$false -and `$true = $result4" # True
# Use parentheses for clarity
$result5 = ($a -or $b) -and $c
Write-Host "(`$true -or `$false) -and `$true = $result5" # True
# Best practice: Use parentheses for complex expressions
$age = 25
$hasLicense = $true
$hasInsurance = $true
# Clear and explicit
if (($age -ge 18) -and ($hasLicense -and $hasInsurance)) {
Write-Host "Eligible to drive"
}
7.10 Special Operators
7.10.1 Range Operator (..)
# Create a range of numbers
$numbers = 1..10
Write-Host "Numbers: $($numbers -join ', ')" # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
# Reverse range
$countdown = 10..1
Write-Host "Countdown: $($countdown -join ', ')"
# Use range in loop
foreach ($i in 1..5) {
Write-Host "Iteration $i"
}
# Create array of years
$years = 2020..2026
Write-Host "Years: $($years -join ', ')"
# Use range for array slicing
$letters = 'a', 'b', 'c', 'd', 'e', 'f'
$subset = $letters[1..3]
Write-Host "Subset: $($subset -join ', ')" # b, c, d
7.10.2 Call Operator (&)
# Execute command stored in variable
$command = "Get-Process"
& $command | Select-Object -First 5
# Execute script from variable
$scriptPath = "C:\Scripts\MyScript.ps1"
if (Test-Path $scriptPath) {
& $scriptPath
}
# Execute command with spaces in path
$program = "C:\Program Files\MyApp\app.exe"
& $program
# Execute with arguments
$exe = "notepad.exe"
$file = "C:\Temp\notes.txt"
& $exe $file
7.10.3 Comma Operator (,)
# Create array using comma operator
$colors = "red", "green", "blue"
Write-Host "Colors: $($colors -join ', ')"
# Single element array
$singleItem = , "only one"
Write-Host "Count: $($singleItem.Count)" # 1
# Nested arrays
$matrix = @(
(1, 2, 3),
(4, 5, 6),
(7, 8, 9)
)
Write-Host "Matrix [1][1]: $($matrix[1][1])" # 5
7.11 Hands-On Exercises
Practice what you've learned with these real-world scenarios.
Exercise 1: Calculator Script
Task: Create a simple calculator that performs basic arithmetic operations based on user input.
Show Solution
# Simple Calculator Script
Write-Host "=== PowerShell Calculator ===" -ForegroundColor Cyan
# Get first number
$num1 = Read-Host "Enter first number"
$num1 = [double]$num1
# Get operator
Write-Host "`nAvailable operators: +, -, *, /, %"
$operator = Read-Host "Enter operator"
# Get second number
$num2 = Read-Host "Enter second number"
$num2 = [double]$num2
# Perform calculation
$result = switch ($operator) {
"+" { $num1 + $num2 }
"-" { $num1 - $num2 }
"*" { $num1 * $num2 }
"/" {
if ($num2 -eq 0) {
Write-Host "Error: Division by zero!" -ForegroundColor Red
return
}
$num1 / $num2
}
"%" { $num1 % $num2 }
default {
Write-Host "Error: Invalid operator!" -ForegroundColor Red
return
}
}
# Display result
Write-Host "`nResult: $num1 $operator $num2 = $result" -ForegroundColor Green
Exercise 2: String Validator
Task: Create a script that validates email addresses, phone numbers, and ZIP codes using regex.
Show Solution
# String Validator Script
function Test-Email {
param([string]$Email)
$pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return $Email -match $pattern
}
function Test-PhoneNumber {
param([string]$Phone)
# Matches formats: 123-456-7890, (123) 456-7890, 1234567890
$pattern = "^(\d{3}[-.]?\d{3}[-.]?\d{4}|\(\d{3}\)\s?\d{3}[-.]?\d{4})$"
return $Phone -match $pattern
}
function Test-ZipCode {
param([string]$Zip)
# Matches 5-digit or 5+4 format
$pattern = "^\d{5}(-\d{4})?$"
return $Zip -match $pattern
}
# Test emails
$emails = @(
"[email protected]",
"invalid.email",
"[email protected]",
"bad@domain"
)
Write-Host "=== Email Validation ===" -ForegroundColor Cyan
foreach ($email in $emails) {
$isValid = Test-Email -Email $email
$status = if ($isValid) { "β Valid" } else { "β Invalid" }
$color = if ($isValid) { "Green" } else { "Red" }
Write-Host "$status : $email" -ForegroundColor $color
}
# Test phone numbers
$phones = @(
"123-456-7890",
"(123) 456-7890",
"1234567890",
"12-345-6789"
)
Write-Host "`n=== Phone Number Validation ===" -ForegroundColor Cyan
foreach ($phone in $phones) {
$isValid = Test-PhoneNumber -Phone $phone
$status = if ($isValid) { "β Valid" } else { "β Invalid" }
$color = if ($isValid) { "Green" } else { "Red" }
Write-Host "$status : $phone" -ForegroundColor $color
}
# Test ZIP codes
$zips = @(
"12345",
"12345-6789",
"1234",
"12345-678"
)
Write-Host "`n=== ZIP Code Validation ===" -ForegroundColor Cyan
foreach ($zip in $zips) {
$isValid = Test-ZipCode -Zip $zip
$status = if ($isValid) { "β Valid" } else { "β Invalid" }
$color = if ($isValid) { "Green" } else { "Red" }
Write-Host "$status : $zip" -ForegroundColor $color
}
Exercise 3: Logical Expression Evaluator
Task: Create a script that evaluates complex logical conditions for access control.
Show Solution
# Access Control Evaluator
function Test-AccessControl {
param(
[int]$Age,
[bool]$IsEmployee,
[bool]$HasBadge,
[string]$Department,
[bool]$IsWeekend
)
# Define access rules
$isAdult = $age -ge 18
$isAuthorizedDept = $Department -in @("IT", "Security", "Management")
$hasProperCredentials = $IsEmployee -and $HasBadge
$isBusinessHours = -not $IsWeekend
# Evaluate access levels
$hasBasicAccess = $isAdult -and $hasProperCredentials
$hasExtendedAccess = $hasBasicAccess -and $isAuthorizedDept
$hasFullAccess = $hasExtendedAccess -or ($IsEmployee -and $Department -eq "Security")
# Weekend access requires special authorization
if ($IsWeekend -and -not ($Department -eq "Security")) {
$hasFullAccess = $false
}
# Return access report
return [PSCustomObject]@{
Age = $Age
IsEmployee = $IsEmployee
HasBadge = $HasBadge
Department = $Department
IsWeekend = $IsWeekend
BasicAccess = $hasBasicAccess
ExtendedAccess = $hasExtendedAccess
FullAccess = $hasFullAccess
}
}
# Test scenarios
Write-Host "=== Access Control Test ===" -ForegroundColor Cyan
$scenarios = @(
@{ Age=25; IsEmployee=$true; HasBadge=$true; Department="IT"; IsWeekend=$false },
@{ Age=17; IsEmployee=$true; HasBadge=$true; Department="IT"; IsWeekend=$false },
@{ Age=30; IsEmployee=$true; HasBadge=$false; Department="IT"; IsWeekend=$false },
@{ Age=35; IsEmployee=$true; HasBadge=$true; Department="HR"; IsWeekend=$false },
@{ Age=40; IsEmployee=$true; HasBadge=$true; Department="Security"; IsWeekend=$true }
)
$counter = 1
foreach ($scenario in $scenarios) {
Write-Host "`nScenario $counter" -ForegroundColor Yellow
$result = Test-AccessControl @scenario
$result | Format-List
$counter++
}
7.12 Chapter Summary
Key Takeaways
- Arithmetic Operators - Perform mathematical calculations (+, -, *, /, %)
- Assignment Operators - Assign and modify values (=, +=, -=, *=, /=, %=)
- Comparison Operators - Compare values (-eq, -ne, -gt, -lt, -ge, -le)
- Logical Operators - Combine conditions (-and, -or, -not, -xor)
- String Operators - Manipulate text (-like, -match, -replace, -split, -join)
- Type Operators - Check and convert types (-is, -isnot, -as)
- Containment Operators - Check collection membership (-contains, -in)
- Operator Precedence - Use parentheses for clarity and control
- Case Sensitivity - Use -c prefix for case-sensitive operations
- Regular Expressions - Powerful pattern matching with -match and -replace
Operator Quick Reference
| Category | Operators |
|---|---|
| Arithmetic | +, -, *, /, % |
| Assignment | =, +=, -=, *=, /=, %= |
| Comparison | -eq, -ne, -gt, -ge, -lt, -le |
| Logical | -and, -or, -not, -xor |
| String | -like, -match, -replace, -split, -join |
| Type | -is, -isnot, -as |
| Containment | -contains, -notcontains, -in, -notin |
7.13 Chapter Quiz
1. What is the result of: 10 + 5 * 2?
A) 30
B) 20
C) 25
D) 15
2. Which operator checks if a string matches a wildcard pattern?
A) -match
B) -like
C) -eq
D) -contains
3. What does $true -xor $true return?
A) $true
B) $false
C) $null
D) Error
4. How do you perform case-sensitive string comparison?
A) Use -eq
B) Use -ceq
C) Use -match
D) Use -like
5. What does "PowerShell" -replace "Shell", "Script" return?
A) "PowerScript"
B) "PowerShell"
C) "Script"
D) Error
6. Which operator checks if a variable is of type [int]?
A) -eq
B) -is
C) -as
D) -type
7. What is the result of: @(1,2,3) -contains 2?
A) $true
B) $false
C) 2
D) @(2)
8. How do you split a string "a,b,c" into an array?
A) "a,b,c" -split ","
B) "a,b,c".Split(",")
C) Split-String "a,b,c" -Delimiter ","
D) Both A and B
9. What does $x += 5 do?
A) Assigns 5 to $x
B) Adds 5 to $x and stores result in $x
C) Compares $x with 5
D) Creates array with $x and 5
10. What does "test123" -match "\d+" return?
A) $true
B) $false
C) "123"
D) @("1","2","3")
Chapter 8: Control Flow (If, Switch, Loops)
Learning Objectives
- Master conditional statements (if/elseif/else)
- Use switch statements for multi-way branching
- Implement various loop types (for, foreach, while, do-while)
- Control loop execution with break and continue
- Apply control flow in real-world scenarios
8.1 Introduction to Control Flow
Control flow structures allow your scripts to make decisions and repeat actions. They are the foundation of programming logic and enable you to write dynamic, responsive scripts.
8.2 If Statements
The if statement executes code based on a condition. If the condition evaluates to $true, the code block runs.
8.2.1 Basic If Statement
$age = 25
if ($age -ge 18) {
Write-Host "You are an adult."
}
# Output: You are an adult.
8.2.2 If-Else Statement
$temperature = 15
if ($temperature -gt 20) {
Write-Host "It's warm outside."
} else {
Write-Host "It's cold outside."
}
# Output: It's cold outside.
8.2.3 If-ElseIf-Else Statement
$score = 85
if ($score -ge 90) {
$grade = "A"
} elseif ($score -ge 80) {
$grade = "B"
} elseif ($score -ge 70) {
$grade = "C"
} elseif ($score -ge 60) {
$grade = "D"
} else {
$grade = "F"
}
Write-Host "Your grade is: $grade"
# Output: Your grade is: B
8.2.4 Nested If Statements
$username = "admin"
$password = "secret123"
if ($username -eq "admin") {
if ($password -eq "secret123") {
Write-Host "Login successful!"
} else {
Write-Host "Invalid password."
}
} else {
Write-Host "Invalid username."
}
# Output: Login successful!
8.2.5 Logical Operators in If Statements
# Using -and
$age = 25
$hasLicense = $true
if ($age -ge 18 -and $hasLicense) {
Write-Host "You can drive."
}
# Using -or
$isWeekend = $false
$isHoliday = $true
if ($isWeekend -or $isHoliday) {
Write-Host "No work today!"
}
# Using -not
$isRaining = $false
if (-not $isRaining) {
Write-Host "You don't need an umbrella."
}
8.3 Switch Statements
The switch statement provides a cleaner way to handle multiple conditions compared to long if-elseif chains.
8.3.1 Basic Switch Statement
$day = "Monday"
switch ($day) {
"Monday" { Write-Host "Start of the work week" }
"Tuesday" { Write-Host "Second day" }
"Wednesday" { Write-Host "Midweek" }
"Thursday" { Write-Host "Almost Friday" }
"Friday" { Write-Host "TGIF!" }
"Saturday" { Write-Host "Weekend!" }
"Sunday" { Write-Host "Rest day" }
default { Write-Host "Invalid day" }
}
# Output: Start of the work week
8.3.2 Switch with Multiple Matches
$number = 2
switch ($number) {
{$_ -lt 0} { Write-Host "Negative number" }
{$_ -eq 0} { Write-Host "Zero" }
{$_ -gt 0} { Write-Host "Positive number" }
{$_ % 2 -eq 0} { Write-Host "Even number" }
{$_ % 2 -ne 0} { Write-Host "Odd number" }
}
# Output:
# Positive number
# Even number
8.3.3 Switch with Wildcard Matching
$filename = "document.txt"
switch -Wildcard ($filename) {
"*.txt" { Write-Host "Text file" }
"*.pdf" { Write-Host "PDF file" }
"*.docx" { Write-Host "Word document" }
"*.jpg" { Write-Host "Image file" }
default { Write-Host "Unknown file type" }
}
# Output: Text file
8.3.4 Switch with Regex Matching
$email = "[email protected]"
switch -Regex ($email) {
"^[a-z]+" { Write-Host "Starts with lowercase letters" }
"@" { Write-Host "Contains @ symbol" }
"\.com$" { Write-Host "Ends with .com" }
"^\w+@\w+\.\w+$" { Write-Host "Valid email format" }
}
# Output:
# Starts with lowercase letters
# Contains @ symbol
# Ends with .com
# Valid email format
8.3.5 Switch with Arrays
$numbers = 1, 2, 3, 4, 5
switch ($numbers) {
{$_ % 2 -eq 0} { Write-Host "$_ is even" }
{$_ % 2 -ne 0} { Write-Host "$_ is odd" }
}
# Output:
# 1 is odd
# 2 is even
# 3 is odd
# 4 is even
# 5 is odd
8.4 For Loops
The for loop repeats a block of code a specific number of times.
8.4.1 Basic For Loop
# Count from 1 to 5
for ($i = 1; $i -le 5; $i++) {
Write-Host "Count: $i"
}
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5
8.4.2 For Loop with Custom Increment
# Count by 2s
for ($i = 0; $i -le 10; $i += 2) {
Write-Host $i
}
# Output: 0 2 4 6 8 10
# Count backwards
for ($i = 10; $i -ge 1; $i--) {
Write-Host $i
}
# Output: 10 9 8 7 6 5 4 3 2 1
8.4.3 Nested For Loops
# Create a multiplication table
for ($i = 1; $i -le 5; $i++) {
for ($j = 1; $j -le 5; $j++) {
$result = $i * $j
Write-Host "$i x $j = $result"
}
Write-Host "---"
}
8.5 ForEach Loops
The foreach loop iterates through each item in a collection.
8.5.1 Basic ForEach Loop
$fruits = "Apple", "Banana", "Orange", "Grape"
foreach ($fruit in $fruits) {
Write-Host "I like $fruit"
}
# Output:
# I like Apple
# I like Banana
# I like Orange
# I like Grape
8.5.2 ForEach with Hashtables
$ages = @{
"John" = 25
"Jane" = 30
"Bob" = 35
}
foreach ($person in $ages.Keys) {
Write-Host "$person is $($ages[$person]) years old"
}
# Output:
# John is 25 years old
# Jane is 30 years old
# Bob is 35 years old
8.5.3 ForEach-Object Cmdlet
# Using ForEach-Object (alias: foreach, %)
1..5 | ForEach-Object {
Write-Host "Number: $_"
}
# Shorter syntax
1..5 | % { Write-Host "Number: $_" }
# Processing files
Get-ChildItem -Path C:\Temp -Filter *.txt | ForEach-Object {
Write-Host "File: $($_.Name), Size: $($_.Length) bytes"
}
8.6 While Loops
The while loop repeats as long as a condition is true.
8.6.1 Basic While Loop
$count = 1
while ($count -le 5) {
Write-Host "Count: $count"
$count++
}
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5
8.6.2 While Loop with User Input
$password = ""
while ($password -ne "secret") {
$password = Read-Host "Enter password"
if ($password -ne "secret") {
Write-Host "Incorrect password. Try again."
}
}
Write-Host "Access granted!"
8.7 Do-While and Do-Until Loops
These loops execute at least once before checking the condition.
8.7.1 Do-While Loop
$count = 1
do {
Write-Host "Count: $count"
$count++
} while ($count -le 5)
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5
8.7.2 Do-Until Loop
$count = 1
do {
Write-Host "Count: $count"
$count++
} until ($count -gt 5)
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5
8.7.3 Difference Between While and Do-While
# While loop - may not execute at all
$count = 10
while ($count -lt 5) {
Write-Host "This won't print"
}
# Do-While loop - executes at least once
$count = 10
do {
Write-Host "This prints once: $count"
} while ($count -lt 5)
# Output: This prints once: 10
8.8 Break and Continue
Control loop execution with break (exit loop) and continue (skip to next iteration).
8.8.1 Using Break
# Exit loop when condition is met
for ($i = 1; $i -le 10; $i++) {
if ($i -eq 5) {
Write-Host "Breaking at $i"
break
}
Write-Host $i
}
# Output:
# 1
# 2
# 3
# 4
# Breaking at 5
8.8.2 Using Continue
# Skip even numbers
for ($i = 1; $i -le 10; $i++) {
if ($i % 2 -eq 0) {
continue
}
Write-Host $i
}
# Output: 1 3 5 7 9
8.8.3 Break with Labels
:outer for ($i = 1; $i -le 3; $i++) {
for ($j = 1; $j -le 3; $j++) {
if ($i -eq 2 -and $j -eq 2) {
Write-Host "Breaking outer loop at i=$i, j=$j"
break outer
}
Write-Host "i=$i, j=$j"
}
}
# Output:
# i=1, j=1
# i=1, j=2
# i=1, j=3
# i=2, j=1
# Breaking outer loop at i=2, j=2
8.9 Control Flow Best Practices
Best Practices for Control Flow
- Keep it simple: Avoid deeply nested conditions (max 3 levels)
- Use switch for multiple conditions: More readable than long if-elseif chains
- Choose the right loop:
- Use
foreachfor collections - Use
forwhen you need an index - Use
whilewhen iterations are unknown
- Use
- Avoid infinite loops: Always ensure loop conditions can become false
- Use meaningful variable names:
$userAgeinstead of$x - Comment complex logic: Explain why, not what
- Use early returns: Exit functions early to reduce nesting
8.10 Real-World Examples
8.10.1 File Processing Script
$files = Get-ChildItem -Path C:\Temp
foreach ($file in $files) {
switch -Wildcard ($file.Extension) {
".txt" {
Write-Host "Text file: $($file.Name)"
# Process text file
}
".log" {
Write-Host "Log file: $($file.Name)"
# Archive log file
}
".tmp" {
Write-Host "Temp file: $($file.Name) - Deleting"
# Remove-Item $file.FullName
}
default {
Write-Host "Other file: $($file.Name)"
}
}
}
8.10.2 Service Monitor Script
$services = "Spooler", "wuauserv", "BITS"
foreach ($serviceName in $services) {
$service = Get-Service -Name $serviceName
if ($service.Status -eq "Running") {
Write-Host "$serviceName is running - OK" -ForegroundColor Green
} else {
Write-Host "$serviceName is stopped - Attempting restart" -ForegroundColor Yellow
try {
Start-Service -Name $serviceName
Write-Host "$serviceName restarted successfully" -ForegroundColor Green
} catch {
Write-Host "Failed to restart $serviceName" -ForegroundColor Red
}
}
}
8.10.3 User Input Validation
$maxAttempts = 3
$attempts = 0
$validInput = $false
do {
$age = Read-Host "Enter your age (1-120)"
$attempts++
if ($age -match '^\d+$') {
$ageNum = [int]$age
if ($ageNum -ge 1 -and $ageNum -le 120) {
Write-Host "Valid age: $ageNum" -ForegroundColor Green
$validInput = $true
} else {
Write-Host "Age must be between 1 and 120" -ForegroundColor Red
}
} else {
Write-Host "Please enter a valid number" -ForegroundColor Red
}
if (-not $validInput -and $attempts -lt $maxAttempts) {
Write-Host "Attempts remaining: $($maxAttempts - $attempts)"
}
} while (-not $validInput -and $attempts -lt $maxAttempts)
if (-not $validInput) {
Write-Host "Maximum attempts reached. Exiting." -ForegroundColor Red
}
8.11 Hands-On Exercises
Exercise 1: Grade Calculator
Task: Create a script that accepts multiple student scores and calculates letter grades.
Requirements:
- Accept scores from 0-100
- Calculate letter grade (A: 90-100, B: 80-89, C: 70-79, D: 60-69, F: 0-59)
- Display grade distribution
- Calculate class average
Show Solution
$scores = @()
$gradeCount = @{
"A" = 0
"B" = 0
"C" = 0
"D" = 0
"F" = 0
}
# Collect scores
$numStudents = Read-Host "How many students?"
for ($i = 1; $i -le $numStudents; $i++) {
do {
$score = Read-Host "Enter score for student $i (0-100)"
$isValid = $score -match '^\d+$' -and [int]$score -ge 0 -and [int]$score -le 100
if (-not $isValid) {
Write-Host "Invalid score. Please enter 0-100" -ForegroundColor Red
}
} while (-not $isValid)
$scores += [int]$score
}
# Calculate grades
foreach ($score in $scores) {
$grade = switch ($score) {
{$_ -ge 90} { "A"; break }
{$_ -ge 80} { "B"; break }
{$_ -ge 70} { "C"; break }
{$_ -ge 60} { "D"; break }
default { "F" }
}
$gradeCount[$grade]++
Write-Host "Score: $score = Grade: $grade"
}
# Display statistics
Write-Host "`n--- Grade Distribution ---"
foreach ($grade in $gradeCount.Keys | Sort-Object) {
Write-Host "$grade: $($gradeCount[$grade]) students"
}
$average = ($scores | Measure-Object -Average).Average
Write-Host "`nClass Average: $([math]::Round($average, 2))"
Exercise 2: Number Guessing Game
Task: Create a number guessing game where the computer picks a random number.
Requirements:
- Computer picks random number 1-100
- User has 7 attempts to guess
- Provide "higher" or "lower" hints
- Track number of attempts
Show Solution
$secretNumber = Get-Random -Minimum 1 -Maximum 101
$maxAttempts = 7
$attempts = 0
$won = $false
Write-Host "=== Number Guessing Game ===" -ForegroundColor Cyan
Write-Host "I'm thinking of a number between 1 and 100"
Write-Host "You have $maxAttempts attempts to guess it!`n"
do {
$attempts++
$remainingAttempts = $maxAttempts - $attempts + 1
$guess = Read-Host "Attempt $attempts/$maxAttempts - Enter your guess"
if ($guess -notmatch '^\d+$') {
Write-Host "Please enter a valid number!" -ForegroundColor Red
$attempts--
continue
}
$guessNum = [int]$guess
if ($guessNum -lt 1 -or $guessNum -gt 100) {
Write-Host "Number must be between 1 and 100!" -ForegroundColor Red
$attempts--
continue
}
if ($guessNum -eq $secretNumber) {
Write-Host "`nCongratulations! You guessed it in $attempts attempts!" -ForegroundColor Green
$won = $true
} elseif ($guessNum -lt $secretNumber) {
Write-Host "Too low! Try higher. ($remainingAttempts attempts left)" -ForegroundColor Yellow
} else {
Write-Host "Too high! Try lower. ($remainingAttempts attempts left)" -ForegroundColor Yellow
}
} while (-not $won -and $attempts -lt $maxAttempts)
if (-not $won) {
Write-Host "`nGame Over! The number was $secretNumber" -ForegroundColor Red
}
Exercise 3: Disk Space Monitor
Task: Create a script that monitors disk space and alerts when space is low.
Requirements:
- Check all local drives
- Calculate free space percentage
- Alert if free space < 20% (Warning) or < 10% (Critical)
- Display results in color-coded format
Show Solution
Write-Host "=== Disk Space Monitor ===" -ForegroundColor Cyan
Write-Host ""
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -ne $null }
foreach ($drive in $drives) {
$totalGB = [math]::Round($drive.Used / 1GB + $drive.Free / 1GB, 2)
$usedGB = [math]::Round($drive.Used / 1GB, 2)
$freeGB = [math]::Round($drive.Free / 1GB, 2)
$freePercent = [math]::Round(($drive.Free / ($drive.Used + $drive.Free)) * 100, 2)
Write-Host "Drive: $($drive.Name):" -NoNewline
Write-Host " | Total: $totalGB GB | Used: $usedGB GB | Free: $freeGB GB" -NoNewline
# Determine status and color
$status = ""
$color = "Green"
if ($freePercent -lt 10) {
$status = "CRITICAL"
$color = "Red"
} elseif ($freePercent -lt 20) {
$status = "WARNING"
$color = "Yellow"
} else {
$status = "OK"
$color = "Green"
}
Write-Host " | Free: $freePercent% " -NoNewline
Write-Host "[$status]" -ForegroundColor $color
# Visual bar
$barLength = 50
$usedBars = [math]::Round(($barLength * (100 - $freePercent)) / 100)
$freeBars = $barLength - $usedBars
Write-Host " [" -NoNewline
Write-Host ("β" * $usedBars) -NoNewline -ForegroundColor Red
Write-Host ("β" * $freeBars) -NoNewline -ForegroundColor Green
Write-Host "]"
Write-Host ""
}
Write-Host "Monitoring complete." -ForegroundColor Cyan
8.12 Chapter Summary
Key Takeaways
- If statements execute code based on conditions (if/elseif/else)
- Switch statements provide cleaner multi-way branching with support for wildcards and regex
- For loops iterate a specific number of times with index control
- ForEach loops iterate through collections without manual indexing
- While loops repeat while a condition is true (may not execute at all)
- Do-While/Do-Until loops execute at least once before checking condition
- Break exits a loop immediately
- Continue skips to the next iteration
- Labels allow breaking out of nested loops
- Choose the right control structure for readability and efficiency
8.13 Chapter Quiz
Question 1: What is the output of this code?
$x = 5
if ($x -gt 10) {
Write-Host "A"
} elseif ($x -gt 3) {
Write-Host "B"
} else {
Write-Host "C"
}
Show Answer
Answer: B
Explanation: $x is 5, which is not greater than 10, but is greater than 3, so the elseif block executes.
Question 2: Which loop type guarantees at least one execution?
Show Answer
Answer: Do-While and Do-Until loops
Explanation: These loops check the condition after executing the code block, ensuring at least one iteration.
Question 3: What does the continue statement do?
Show Answer
Answer: Skips the rest of the current iteration and moves to the next iteration
Explanation: continue jumps to the next iteration without executing remaining code in the loop body. break exits the loop entirely.
Question 4: How many times will this loop execute?
for ($i = 0; $i -lt 10; $i += 2) {
Write-Host $i
}
Show Answer
Answer: 5 times
Explanation: The loop starts at 0 and increments by 2 each time: 0, 2, 4, 6, 8 (5 iterations).
Question 5: What is the advantage of switch over if-elseif?
Show Answer
Answer: Better readability, supports wildcards/regex, can match multiple conditions, processes arrays
Explanation: Switch statements are more readable for multiple conditions and offer powerful matching options.
Question 6: What will this output?
$count = 10
while ($count -lt 5) {
Write-Host $count
$count++
}
Show Answer
Answer: Nothing (no output)
Explanation: The condition ($count -lt 5) is false from the start, so the loop never executes.
Question 7: When should you use foreach instead of for?
Show Answer
Answer: When iterating through collections where you don't need the index
Explanation: Foreach is cleaner and more readable for simple collection iteration. Use for when you need index control.
Question 8: What does the -Wildcard parameter do in switch statements?
Show Answer
Answer: Enables pattern matching using * and ? wildcards
Explanation: -Wildcard allows matching patterns like "*.txt" or "test?.log".
Question 9: How do you break out of a nested loop?
Show Answer
Answer: Use labeled break statements (e.g., break outerLoop)
Explanation: Labels allow you to specify which loop to break from in nested structures.
Question 10: What's the difference between do-while and do-until?
Show Answer
Answer: do-while continues while condition is true; do-until continues until condition is true
Explanation: They are logical opposites. do-while loops while true, do-until loops until true (while false).
Chapter 9: Functions and Scripts
Learning Objectives
- Understand the purpose and benefits of functions
- Create basic and advanced functions
- Work with parameters and return values
- Use advanced parameter features
- Understand function scope
- Create and manage PowerShell scripts
- Apply best practices for functions and scripts
9.1 Introduction to Functions
Functions are reusable blocks of code that perform specific tasks. They help you:
- Organize code: Break complex scripts into manageable pieces
- Reuse code: Write once, use many times
- Improve readability: Give meaningful names to code blocks
- Simplify testing: Test individual functions independently
- Enable modularity: Share functions across scripts
function FunctionName {
# Function body
# Code to execute
}
9.2 Creating Basic Functions
Let's start with simple functions:
# Define a simple function
function Say-Hello {
Write-Host "Hello, PowerShell!"
}
# Call the function
Say-Hello
# Output: Hello, PowerShell!
# Function that performs a task
function Get-SystemInfo {
$os = Get-CimInstance Win32_OperatingSystem
$computer = Get-CimInstance Win32_ComputerSystem
Write-Host "Computer Name: $($computer.Name)"
Write-Host "OS: $($os.Caption)"
Write-Host "Version: $($os.Version)"
Write-Host "Total Memory: $([math]::Round($computer.TotalPhysicalMemory/1GB, 2)) GB"
}
# Call the function
Get-SystemInfo
9.3 Functions with Parameters
Parameters make functions flexible by accepting input values:
# Function with one parameter
function Say-HelloTo {
param($Name)
Write-Host "Hello, $Name!"
}
# Call with argument
Say-HelloTo -Name "Alice"
# Output: Hello, Alice!
# Positional argument (no parameter name)
Say-HelloTo "Bob"
# Output: Hello, Bob!
# Function with multiple parameters
function Get-FullName {
param(
$FirstName,
$LastName
)
return "$FirstName $LastName"
}
# Call the function
$name = Get-FullName -FirstName "John" -LastName "Doe"
Write-Host "Full Name: $name"
# Output: Full Name: John Doe
# Function with default parameter values
function Greet-User {
param(
$Name = "Guest",
$Greeting = "Hello"
)
Write-Host "$Greeting, $Name!"
}
# Call with no arguments (uses defaults)
Greet-User
# Output: Hello, Guest!
# Call with one argument
Greet-User -Name "Alice"
# Output: Hello, Alice!
# Call with both arguments
Greet-User -Name "Bob" -Greeting "Hi"
# Output: Hi, Bob!
9.4 Return Values
Functions can return values using the return keyword or by outputting objects:
# Function with explicit return
function Add-Numbers {
param($a, $b)
return $a + $b
}
$result = Add-Numbers -a 5 -b 3
Write-Host "Result: $result"
# Output: Result: 8
# Function with implicit return
function Multiply-Numbers {
param($a, $b)
$a * $b # This value is automatically returned
}
$result = Multiply-Numbers -a 4 -b 7
Write-Host "Result: $result"
# Output: Result: 28
# Function returning multiple values
function Get-Statistics {
param([int[]]$Numbers)
$sum = ($Numbers | Measure-Object -Sum).Sum
$average = ($Numbers | Measure-Object -Average).Average
$max = ($Numbers | Measure-Object -Maximum).Maximum
$min = ($Numbers | Measure-Object -Minimum).Minimum
# Return as hashtable
return @{
Sum = $sum
Average = $average
Maximum = $max
Minimum = $min
}
}
$stats = Get-Statistics -Numbers @(10, 20, 30, 40, 50)
Write-Host "Sum: $($stats.Sum)"
Write-Host "Average: $($stats.Average)"
Write-Host "Max: $($stats.Maximum)"
Write-Host "Min: $($stats.Minimum)"
9.5 Advanced Parameters
PowerShell provides advanced parameter features for robust functions:
9.5.1 Mandatory Parameters
# Function with mandatory parameters
function New-User {
param(
[Parameter(Mandatory=$true)]
[string]$Username,
[Parameter(Mandatory=$true)]
[string]$Email
)
Write-Host "Creating user: $Username"
Write-Host "Email: $Email"
}
# This will prompt for missing parameters
New-User -Username "jdoe"
9.5.2 Parameter Validation
# Function with parameter validation
function Set-ServerPort {
param(
[Parameter(Mandatory=$true)]
[ValidateRange(1, 65535)]
[int]$Port,
[Parameter(Mandatory=$true)]
[ValidateSet("HTTP", "HTTPS", "FTP", "SSH")]
[string]$Protocol,
[Parameter(Mandatory=$true)]
[ValidateLength(3, 50)]
[string]$ServerName
)
Write-Host "Configuring $ServerName"
Write-Host "Protocol: $Protocol on port $Port"
}
# Valid call
Set-ServerPort -Port 443 -Protocol "HTTPS" -ServerName "WebServer01"
# Invalid calls will produce errors:
# Set-ServerPort -Port 99999 -Protocol "HTTPS" -ServerName "Web" # Port out of range
# Set-ServerPort -Port 443 -Protocol "SMTP" -ServerName "Web" # Invalid protocol
# Function with various validation attributes
function New-Employee {
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[Parameter(Mandatory=$true)]
[ValidatePattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
[string]$Email,
[Parameter(Mandatory=$true)]
[ValidateRange(18, 65)]
[int]$Age,
[ValidateSet("IT", "HR", "Finance", "Sales", "Marketing")]
[string]$Department = "IT"
)
Write-Host "Employee: $Name"
Write-Host "Email: $Email"
Write-Host "Age: $Age"
Write-Host "Department: $Department"
}
# Valid call
New-Employee -Name "Alice Smith" -Email "[email protected]" -Age 30 -Department "HR"
9.5.3 Parameter Sets
# Function with parameter sets
function Get-UserInfo {
[CmdletBinding(DefaultParameterSetName='ByUsername')]
param(
[Parameter(ParameterSetName='ByUsername', Mandatory=$true)]
[string]$Username,
[Parameter(ParameterSetName='ByID', Mandatory=$true)]
[int]$UserID,
[Parameter(ParameterSetName='ByEmail', Mandatory=$true)]
[string]$Email
)
switch ($PSCmdlet.ParameterSetName) {
'ByUsername' { Write-Host "Looking up user by username: $Username" }
'ByID' { Write-Host "Looking up user by ID: $UserID" }
'ByEmail' { Write-Host "Looking up user by email: $Email" }
}
}
# Use different parameter sets
Get-UserInfo -Username "jdoe"
Get-UserInfo -UserID 12345
Get-UserInfo -Email "[email protected]"
9.6 Function Scope
Understanding variable scope in functions is crucial:
# Script-level variable
$scriptVar = "I'm in script scope"
function Test-Scope {
# Local variable (only exists in function)
$localVar = "I'm local to the function"
# Access script variable
Write-Host "Script variable: $scriptVar"
Write-Host "Local variable: $localVar"
# Modify script variable using scope modifier
$script:scriptVar = "Modified from function"
}
Test-Scope
Write-Host "Script variable after function: $scriptVar"
# Write-Host $localVar # This would error - localVar doesn't exist here
# Demonstrating scope modifiers
$global:globalVar = "Global"
$script:scriptVar = "Script"
function Outer-Function {
$outerVar = "Outer"
function Inner-Function {
$innerVar = "Inner"
Write-Host "Inner can see: $innerVar"
Write-Host "Inner can see outer: $outerVar"
Write-Host "Inner can see script: $script:scriptVar"
Write-Host "Inner can see global: $global:globalVar"
# Modify outer scope
$script:scriptVar = "Modified by Inner"
}
Inner-Function
Write-Host "Outer after Inner: $script:scriptVar"
}
Outer-Function
9.7 Advanced Function Features
9.7.1 Pipeline Support
# Function that accepts pipeline input
function Convert-ToUpperCase {
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]$Text
)
process {
$Text.ToUpper()
}
}
# Use with pipeline
"hello", "world", "powershell" | Convert-ToUpperCase
# Output: HELLO, WORLD, POWERSHELL
# Direct call
Convert-ToUpperCase -Text "test"
# Output: TEST
# Function with begin, process, end blocks
function Get-Sum {
param(
[Parameter(ValueFromPipeline=$true)]
[int]$Number
)
begin {
Write-Host "Starting sum calculation..."
$total = 0
}
process {
$total += $Number
Write-Host "Adding $Number, running total: $total"
}
end {
Write-Host "Final sum: $total"
return $total
}
}
# Use with pipeline
$result = 1, 2, 3, 4, 5 | Get-Sum
Write-Host "Result: $result"
9.7.2 Comment-Based Help
function Get-DiskInfo {
<#
.SYNOPSIS
Gets disk information for specified computers.
.DESCRIPTION
The Get-DiskInfo function retrieves disk space information including
total size, free space, and usage percentage for all drives on one
or more computers.
.PARAMETER ComputerName
The name of the computer(s) to query. Defaults to local computer.
.PARAMETER DriveType
Filter by drive type (3 = Local Disk, 5 = CD-ROM). Default is 3.
.EXAMPLE
Get-DiskInfo
Gets disk info for the local computer.
.EXAMPLE
Get-DiskInfo -ComputerName "Server01", "Server02"
Gets disk info for multiple computers.
.NOTES
Author: Your Name
Version: 1.0
#>
param(
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$DriveType = 3
)
foreach ($computer in $ComputerName) {
Get-CimInstance -ClassName Win32_LogicalDisk -ComputerName $computer |
Where-Object { $_.DriveType -eq $DriveType } |
Select-Object DeviceID,
@{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}},
@{Name="FreeGB";Expression={[math]::Round($_.FreeSpace/1GB,2)}},
@{Name="UsedPercent";Expression={[math]::Round(($_.Size-$_.FreeSpace)/$_.Size*100,2)}}
}
}
# View help
Get-Help Get-DiskInfo -Full
9.8 Working with Scripts
PowerShell scripts are files with .ps1 extension containing PowerShell commands:
9.8.1 Creating Scripts
# MyScript.ps1
# Script description and metadata
param(
[string]$Name = "World"
)
# Script body
Write-Host "Hello, $Name!"
Write-Host "This script is running from: $PSScriptRoot"
# Functions can be defined in scripts
function Get-ScriptInfo {
Write-Host "Script: $($MyInvocation.MyCommand.Name)"
Write-Host "Path: $PSCommandPath"
}
Get-ScriptInfo
9.8.2 Running Scripts
# Method 1: Full path
C:\Scripts\MyScript.ps1
# Method 2: Relative path
.\MyScript.ps1
# Method 3: With parameters
.\MyScript.ps1 -Name "Alice"
# Method 4: Using Invoke-Expression
Invoke-Expression ".\MyScript.ps1"
# Method 5: Dot sourcing (loads functions into current scope)
. .\MyScript.ps1
9.8.3 Execution Policy
# Check current execution policy
Get-ExecutionPolicy
# Set execution policy (requires admin)
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# Execution policy levels:
# - Restricted: No scripts allowed
# - AllSigned: Only signed scripts
# - RemoteSigned: Local scripts OK, remote must be signed
# - Unrestricted: All scripts allowed (with warning)
# - Bypass: Nothing blocked, no warnings
# Temporarily bypass for one script
powershell.exe -ExecutionPolicy Bypass -File .\MyScript.ps1
9.9 Script Best Practices
Best Practices for Functions and Scripts
- Use Verb-Noun naming: Follow PowerShell conventions (Get-User, Set-Config)
- Add comment-based help: Document your functions with .SYNOPSIS, .DESCRIPTION, etc.
- Validate parameters: Use validation attributes to catch errors early
- Use proper scoping: Be explicit about variable scope to avoid bugs
- Handle errors: Use try/catch blocks for robust error handling
- Return objects, not text: Return structured data for pipeline compatibility
- Support pipeline input: Make functions work with the pipeline when appropriate
- Use CmdletBinding: Add [CmdletBinding()] for advanced function features
- Keep functions focused: Each function should do one thing well
- Test thoroughly: Test functions with various inputs and edge cases
9.10 Real-World Examples
Example 1: User Management Function
function New-LocalUserAccount {
<#
.SYNOPSIS
Creates a new local user account with specified properties.
.DESCRIPTION
Creates a local user account with validation and error handling.
Supports password generation and group membership.
.EXAMPLE
New-LocalUserAccount -Username "jdoe" -FullName "John Doe" -Description "IT Staff"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateLength(3, 20)]
[string]$Username,
[Parameter(Mandatory=$true)]
[string]$FullName,
[string]$Description = "",
[SecureString]$Password,
[switch]$GeneratePassword
)
begin {
Write-Verbose "Starting user creation process..."
}
process {
try {
# Generate password if requested
if ($GeneratePassword) {
$Password = ConvertTo-SecureString "P@ssw0rd$(Get-Random -Maximum 9999)" -AsPlainText -Force
Write-Host "Generated password for $Username"
}
# Validate password exists
if (-not $Password) {
throw "Password is required. Use -Password or -GeneratePassword"
}
# Create user
$user = New-LocalUser -Name $Username `
-Password $Password `
-FullName $FullName `
-Description $Description `
-ErrorAction Stop
Write-Host "Successfully created user: $Username" -ForegroundColor Green
return $user
} catch {
Write-Error "Failed to create user $Username : $_"
return $null
}
}
end {
Write-Verbose "User creation process completed."
}
}
# Usage
New-LocalUserAccount -Username "testuser" -FullName "Test User" -GeneratePassword -Verbose
Example 2: Log File Analyzer
function Analyze-LogFile {
<#
.SYNOPSIS
Analyzes log files for errors, warnings, and patterns.
.DESCRIPTION
Reads log files and provides statistics on errors, warnings,
and custom patterns. Supports multiple file formats.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[ValidateScript({Test-Path $_})]
[string]$LogPath,
[string[]]$ErrorPatterns = @("ERROR", "FAIL", "EXCEPTION"),
[string[]]$WarningPatterns = @("WARN", "WARNING"),
[switch]$ShowDetails
)
process {
Write-Host "Analyzing log file: $LogPath" -ForegroundColor Cyan
# Read log file
$logContent = Get-Content -Path $LogPath
$totalLines = $logContent.Count
# Initialize counters
$errorCount = 0
$warningCount = 0
$errorLines = @()
$warningLines = @()
# Analyze each line
foreach ($line in $logContent) {
# Check for errors
foreach ($pattern in $ErrorPatterns) {
if ($line -match $pattern) {
$errorCount++
if ($ShowDetails) {
$errorLines += $line
}
break
}
}
# Check for warnings
foreach ($pattern in $WarningPatterns) {
if ($line -match $pattern) {
$warningCount++
if ($ShowDetails) {
$warningLines += $line
}
break
}
}
}
# Create result object
$result = [PSCustomObject]@{
LogFile = $LogPath
TotalLines = $totalLines
ErrorCount = $errorCount
WarningCount = $warningCount
ErrorPercentage = [math]::Round(($errorCount / $totalLines) * 100, 2)
WarningPercentage = [math]::Round(($warningCount / $totalLines) * 100, 2)
AnalysisDate = Get-Date
}
# Display results
Write-Host "`nAnalysis Results:" -ForegroundColor Yellow
Write-Host "Total Lines: $totalLines"
Write-Host "Errors: $errorCount ($($result.ErrorPercentage)%)" -ForegroundColor Red
Write-Host "Warnings: $warningCount ($($result.WarningPercentage)%)" -ForegroundColor Yellow
# Show details if requested
if ($ShowDetails) {
if ($errorLines.Count -gt 0) {
Write-Host "`nError Lines:" -ForegroundColor Red
$errorLines | ForEach-Object { Write-Host " $_" }
}
if ($warningLines.Count -gt 0) {
Write-Host "`nWarning Lines:" -ForegroundColor Yellow
$warningLines | ForEach-Object { Write-Host " $_" }
}
}
return $result
}
}
# Usage
# Analyze-LogFile -LogPath "C:\Logspp.log" -ShowDetails
Example 3: Backup Script
function Start-FolderBackup {
<#
.SYNOPSIS
Creates a compressed backup of specified folders.
.DESCRIPTION
Backs up folders to a specified location with timestamp and
optional retention policy.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$SourcePath,
[Parameter(Mandatory=$true)]
[string]$BackupPath,
[int]$RetentionDays = 30,
[switch]$Compress
)
begin {
Write-Host "Starting backup process..." -ForegroundColor Cyan
# Create backup directory if it doesn't exist
if (-not (Test-Path $BackupPath)) {
New-Item -Path $BackupPath -ItemType Directory | Out-Null
Write-Host "Created backup directory: $BackupPath"
}
}
process {
try {
# Generate backup name with timestamp
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$sourceName = Split-Path $SourcePath -Leaf
$backupName = "${sourceName}_${timestamp}"
if ($Compress) {
# Create compressed backup
$zipPath = Join-Path $BackupPath "$backupName.zip"
Write-Host "Creating compressed backup: $zipPath"
Compress-Archive -Path $SourcePath -DestinationPath $zipPath -CompressionLevel Optimal
$backupSize = (Get-Item $zipPath).Length / 1MB
Write-Host "Backup created successfully! Size: $([math]::Round($backupSize, 2)) MB" -ForegroundColor Green
} else {
# Create folder backup
$destPath = Join-Path $BackupPath $backupName
Write-Host "Creating folder backup: $destPath"
Copy-Item -Path $SourcePath -Destination $destPath -Recurse -Force
$backupSize = (Get-ChildItem $destPath -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB
Write-Host "Backup created successfully! Size: $([math]::Round($backupSize, 2)) MB" -ForegroundColor Green
}
# Clean old backups
Write-Host "Cleaning backups older than $RetentionDays days..."
$cutoffDate = (Get-Date).AddDays(-$RetentionDays)
Get-ChildItem -Path $BackupPath |
Where-Object { $_.LastWriteTime -lt $cutoffDate } |
ForEach-Object {
Write-Host "Removing old backup: $($_.Name)" -ForegroundColor Yellow
Remove-Item $_.FullName -Recurse -Force
}
Write-Host "Backup process completed successfully!" -ForegroundColor Green
} catch {
Write-Error "Backup failed: $_"
}
}
}
# Usage
# Start-FolderBackup -SourcePath "C:\Important" -BackupPath "D:\Backups" -Compress -RetentionDays 30
9.11 Hands-On Exercises
Exercise 1: Temperature Converter
Task: Create a function that converts temperatures between Celsius, Fahrenheit, and Kelvin.
Requirements:
- Function name:
Convert-Temperature - Parameters:
Value,From,To - Support C, F, K as unit options
- Include parameter validation
- Add comment-based help
Exercise 2: File Organizer
Task: Create a function that organizes files in a folder by extension.
Requirements:
- Function name:
Organize-FilesByExtension - Parameter:
Path(folder to organize) - Create subfolders for each extension
- Move files to appropriate subfolders
- Add -WhatIf support
Exercise 3: System Health Check
Task: Create a comprehensive system health check function.
Requirements:
- Function name:
Test-SystemHealth - Check CPU usage, memory, disk space
- Return health status (Healthy/Warning/Critical)
- Support custom thresholds
- Generate a report object
9.12 Chapter Summary
Key Takeaways
- Functions organize code into reusable, testable blocks
- Parameters make functions flexible with validation attributes
- Return values can be explicit (return) or implicit (output)
- Advanced parameters include mandatory, validation, and parameter sets
- Scope determines variable visibility (local, script, global)
- Pipeline support enables functions to work with PowerShell pipeline
- Comment-based help documents functions for Get-Help
- Scripts are .ps1 files containing PowerShell commands
- Execution policy controls script execution security
- Best practices include Verb-Noun naming, validation, and error handling
9.13 Chapter Quiz
1. What is the correct way to define a function parameter as mandatory?
2. What is the difference between 'return' and implicit output in functions?
3. How do you access a script-level variable from within a function?
4. What attribute makes a function accept pipeline input?
5. What are the three blocks in an advanced function?
6. How do you add help documentation to a function?
7. What is the difference between dot sourcing and regular script execution?
8. What validation attribute restricts a parameter to specific values?
9. What execution policy allows local scripts but requires remote scripts to be signed?
10. What is the recommended naming convention for PowerShell functions?
Chapter 10: Error Handling
Error handling is a critical skill in PowerShell scripting. Proper error handling makes your scripts more robust, reliable, and user-friendly. In this chapter, you'll learn how to anticipate, catch, and handle errors gracefully, ensuring your scripts can recover from unexpected situations.
10.1 Introduction to Error Handling
Error handling allows you to:
- Prevent script crashes from unexpected errors
- Provide meaningful error messages to users
- Log errors for troubleshooting
- Implement retry logic for transient failures
- Clean up resources properly (files, connections, etc.)
- Make scripts production-ready and maintainable
10.2 Understanding Error Types
PowerShell has two types of errors:
10.2.1 Terminating Errors
Errors that stop script execution immediately. These can be caught with try/catch blocks.
# This will stop execution
Get-Item "C:\NonExistent.txt" -ErrorAction Stop
10.2.2 Non-Terminating Errors
Errors that display a message but allow the script to continue.
# This will show error but continue
Get-Item "C:\NonExistent.txt"
Write-Host "Script continues..."
10.3 Try/Catch/Finally Blocks
10.3.1 Basic Try/Catch
try {
# Code that might fail
$file = Get-Item "C:\NonExistent.txt" -ErrorAction Stop
Write-Host "File found: $($file.FullName)"
}
catch {
# Handle the error
Write-Host "Error occurred: $_" -ForegroundColor Red
}
# Output: Error occurred: Cannot find path 'C:\NonExistent.txt'...
10.3.2 Try/Catch with Finally
$file = $null
try {
$file = [System.IO.File]::Open("C:\test.txt", 'Open', 'Read')
$content = [System.IO.StreamReader]::new($file)
Write-Host $content.ReadToEnd()
}
catch {
Write-Host "Error reading file: $_" -ForegroundColor Red
}
finally {
# This ALWAYS executes, even if there's an error
if ($file) {
$file.Close()
Write-Host "File closed"
}
}
10.3.3 Multiple Catch Blocks
try {
$file = Get-Item "C:\test.txt" -ErrorAction Stop
$content = Get-Content $file.FullName -ErrorAction Stop
}
catch [System.IO.FileNotFoundException] {
Write-Host "File not found!" -ForegroundColor Red
}
catch [System.UnauthorizedAccessException] {
Write-Host "Access denied!" -ForegroundColor Red
}
catch {
Write-Host "An unexpected error occurred: $_" -ForegroundColor Red
}
10.4 Error Variables
| Variable | Description | Example |
|---|---|---|
$_ |
Current error object in catch block | $_.Exception.Message |
$Error |
Array of all errors in session | $Error[0] (most recent) |
$? |
Success status of last command | if ($?) { "Success" } |
$LASTEXITCODE |
Exit code of last external program | if ($LASTEXITCODE -eq 0) |
try {
Get-Item "C:\NonExistent.txt" -ErrorAction Stop
}
catch {
Write-Host "Error Message: $($_.Exception.Message)"
Write-Host "Error Type: $($_.Exception.GetType().FullName)"
Write-Host "Line Number: $($_.InvocationInfo.ScriptLineNumber)"
Write-Host "Command: $($_.InvocationInfo.MyCommand)"
}
# Check last command status
Get-Process "NonExistent" -ErrorAction SilentlyContinue
if (-not $?) {
Write-Host "Last command failed"
}
# View all errors
Write-Host "Total errors in session: $($Error.Count)"
$Error[0] # Most recent error
10.5 ErrorAction Parameter
The -ErrorAction parameter controls how cmdlets respond to non-terminating errors:
| Value | Description | Use Case |
|---|---|---|
Stop |
Convert to terminating error | When you want to catch errors |
Continue |
Display error and continue (default) | Normal operation |
SilentlyContinue |
Suppress error and continue | When errors are expected |
Inquire |
Ask user how to proceed | Interactive scripts |
Ignore |
Completely ignore error | Don't even add to $Error |
# Stop - Convert to terminating error
try {
Get-Item "C:\NonExistent.txt" -ErrorAction Stop
}
catch {
Write-Host "Caught error!"
}
# SilentlyContinue - Suppress error message
$file = Get-Item "C:\NonExistent.txt" -ErrorAction SilentlyContinue
if ($null -eq $file) {
Write-Host "File doesn't exist (no error shown)"
}
# Ignore - Don't even add to $Error array
$errorCount = $Error.Count
Get-Item "C:\NonExistent.txt" -ErrorAction Ignore
Write-Host "Error count unchanged: $($Error.Count -eq $errorCount)"
10.6 Throw and Write-Error
10.6.1 Using Throw
Creates a terminating error that stops execution:
function Divide-Numbers {
param([int]$a, [int]$b)
if ($b -eq 0) {
throw "Cannot divide by zero!"
}
return $a / $b
}
try {
$result = Divide-Numbers -a 10 -b 0
}
catch {
Write-Host "Error: $_" -ForegroundColor Red
}
# Throw with custom exception type
if ($age -lt 18) {
throw [System.ArgumentException]::new("Age must be 18 or older")
}
10.6.2 Using Write-Error
Creates a non-terminating error that allows script to continue:
function Process-Files {
param([string[]]$Files)
foreach ($file in $Files) {
if (-not (Test-Path $file)) {
Write-Error "File not found: $file"
continue # Continue processing other files
}
Write-Host "Processing: $file"
# Process file...
}
}
Process-Files -Files @("C:\file1.txt", "C:\missing.txt", "C:\file3.txt")
# Output shows error for missing.txt but continues with file3.txt
10.7 Best Practices for Error Handling
- Always use -ErrorAction Stop with try/catch - Ensures errors are caught
- Catch specific exceptions first - Handle known errors specifically
- Always include a generic catch - Handle unexpected errors
- Use Finally for cleanup - Close files, connections, etc.
- Provide meaningful error messages - Help users understand what went wrong
- Log errors appropriately - Record errors for troubleshooting
- Don't catch errors you can't handle - Let them propagate up
- Test error paths - Ensure error handling works as expected
- Use Write-Error for warnings - Use throw for critical errors
- Include context in errors - Show what operation failed
10.8 Debugging Techniques
10.8.1 Write-Debug and Write-Verbose
function Get-UserData {
[CmdletBinding()]
param([string]$Username)
Write-Verbose "Starting user data retrieval for: $Username"
Write-Debug "Debug point 1: Before validation"
if ([string]::IsNullOrEmpty($Username)) {
Write-Error "Username cannot be empty"
return
}
Write-Debug "Debug point 2: After validation"
Write-Verbose "Querying Active Directory..."
# Simulate data retrieval
$userData = @{
Username = $Username
Email = "[email protected]"
}
Write-Verbose "User data retrieved successfully"
return $userData
}
# Run with verbose output
Get-UserData -Username "john" -Verbose
# Run with debug output (requires $DebugPreference = "Continue")
$DebugPreference = "Continue"
Get-UserData -Username "john" -Debug
$DebugPreference = "SilentlyContinue" # Reset
10.8.2 Set-PSBreakpoint
# Set breakpoint on line number
Set-PSBreakpoint -Script "C:\Scripts\MyScript.ps1" -Line 10
# Set breakpoint on variable access
Set-PSBreakpoint -Variable "errorCount" -Mode ReadWrite
# Set breakpoint on command
Set-PSBreakpoint -Command "Get-Process"
# List all breakpoints
Get-PSBreakpoint
# Remove breakpoints
Remove-PSBreakpoint -Id 1
Get-PSBreakpoint | Remove-PSBreakpoint # Remove all
10.8.3 Start-Transcript
# Start recording all output
Start-Transcript -Path "C:\Logs\script_log.txt"
# Your script commands here
Write-Host "This will be recorded"
Get-Process | Select-Object -First 5
# Stop recording
Stop-Transcript
# The transcript file contains all input and output
10.9 Real-World Error Handling Examples
10.9.1 File Processing with Error Handling
function Process-LogFile {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path,
[string]$OutputPath = "C:\Processed"
)
# Validate input
if (-not (Test-Path $Path)) {
Write-Error "Input file not found: $Path"
return
}
# Create output directory if needed
try {
if (-not (Test-Path $OutputPath)) {
New-Item -Path $OutputPath -ItemType Directory -ErrorAction Stop | Out-Null
Write-Verbose "Created output directory: $OutputPath"
}
}
catch {
Write-Error "Failed to create output directory: $_"
return
}
# Process file
$processedLines = @()
$errorCount = 0
try {
$lines = Get-Content $Path -ErrorAction Stop
foreach ($line in $lines) {
try {
# Process each line
$processed = $line.Trim().ToUpper()
$processedLines += $processed
}
catch {
Write-Warning "Failed to process line: $line"
$errorCount++
}
}
# Save results
$outputFile = Join-Path $OutputPath "processed_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
$processedLines | Set-Content $outputFile -ErrorAction Stop
Write-Host "Processing complete!" -ForegroundColor Green
Write-Host "Processed: $($processedLines.Count) lines"
Write-Host "Errors: $errorCount"
Write-Host "Output: $outputFile"
}
catch {
Write-Error "Fatal error during processing: $_"
}
}
# Usage
Process-LogFile -Path "C:\Logs\app.log" -Verbose
10.9.2 Network Operation with Retry Logic
function Invoke-WebRequestWithRetry {
param(
[string]$Url,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 5
)
$attempt = 0
while ($attempt -lt $MaxRetries) {
$attempt++
try {
Write-Host "Attempt $attempt of $MaxRetries..."
$response = Invoke-WebRequest -Uri $Url -ErrorAction Stop
Write-Host "Success!" -ForegroundColor Green
return $response
}
catch [System.Net.WebException] {
$statusCode = $_.Exception.Response.StatusCode.value__
Write-Warning "Web request failed with status code: $statusCode"
if ($attempt -lt $MaxRetries) {
Write-Host "Retrying in $RetryDelaySeconds seconds..."
Start-Sleep -Seconds $RetryDelaySeconds
}
else {
Write-Error "Max retries reached. Giving up."
throw
}
}
catch {
Write-Error "Unexpected error: $_"
throw
}
}
}
# Usage
try {
$result = Invoke-WebRequestWithRetry -Url "https://api.example.com/data"
Write-Host "Data retrieved: $($result.Content.Length) bytes"
}
catch {
Write-Host "Failed to retrieve data after all retries"
}
10.10 Custom Error Logging
function Write-ErrorLog {
param(
[string]$Message,
[string]$LogPath = "C:\Logs\errors.log",
[ValidateSet("Info", "Warning", "Error", "Critical")]
[string]$Level = "Error"
)
try {
# Create log directory if needed
$logDir = Split-Path $LogPath -Parent
if (-not (Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
}
# Format log entry
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
# Write to log file
Add-Content -Path $LogPath -Value $logEntry -ErrorAction Stop
# Also write to console with color
$color = switch ($Level) {
"Info" { "White" }
"Warning" { "Yellow" }
"Error" { "Red" }
"Critical" { "Magenta" }
}
Write-Host $logEntry -ForegroundColor $color
}
catch {
Write-Warning "Failed to write to log file: $_"
}
}
# Usage in script
try {
# Your code here
Get-Item "C:\NonExistent.txt" -ErrorAction Stop
}
catch {
Write-ErrorLog -Message "File operation failed: $_" -Level "Error"
Write-ErrorLog -Message "Stack trace: $($_.ScriptStackTrace)" -Level "Error"
}
10.11 Hands-On Exercises
Exercise 1: File Copy with Error Handling
Task: Create a function that copies files with comprehensive error handling.
Requirements:
- Validate source file exists
- Create destination directory if needed
- Handle file in use errors
- Provide detailed error messages
- Log all operations
Show Solution
function Copy-FileWithErrorHandling {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Source,
[Parameter(Mandatory)]
[string]$Destination,
[switch]$Force
)
$logFile = "C:\Logs\file_copy_$(Get-Date -Format 'yyyyMMdd').log"
function Write-Log {
param([string]$Message, [string]$Level = "Info")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $logFile -Value $logEntry
$color = if ($Level -eq "Error") { "Red" } elseif ($Level -eq "Warning") { "Yellow" } else { "White" }
Write-Host $logEntry -ForegroundColor $color
}
try {
Write-Log "Starting file copy operation"
Write-Log "Source: $Source"
Write-Log "Destination: $Destination"
# Validate source
if (-not (Test-Path $Source)) {
throw "Source file not found: $Source"
}
Write-Log "Source file validated"
# Create destination directory
$destDir = Split-Path $Destination -Parent
if (-not (Test-Path $destDir)) {
try {
New-Item -Path $destDir -ItemType Directory -Force -ErrorAction Stop | Out-Null
Write-Log "Created destination directory: $destDir"
}
catch {
throw "Failed to create destination directory: $_"
}
}
# Check if destination exists
if ((Test-Path $Destination) -and -not $Force) {
throw "Destination file already exists. Use -Force to overwrite."
}
# Attempt copy
try {
Copy-Item -Path $Source -Destination $Destination -Force:$Force -ErrorAction Stop
Write-Log "File copied successfully" -Level "Info"
# Verify copy
$sourceHash = (Get-FileHash $Source).Hash
$destHash = (Get-FileHash $Destination).Hash
if ($sourceHash -eq $destHash) {
Write-Log "File integrity verified" -Level "Info"
return $true
}
else {
throw "File integrity check failed!"
}
}
catch [System.IO.IOException] {
Write-Log "IO Error: File may be in use or locked: $_" -Level "Error"
throw
}
catch [System.UnauthorizedAccessException] {
Write-Log "Access Denied: Insufficient permissions: $_" -Level "Error"
throw
}
}
catch {
Write-Log "Copy operation failed: $_" -Level "Error"
Write-Log "Stack trace: $($_.ScriptStackTrace)" -Level "Error"
return $false
}
}
# Test the function
Copy-FileWithErrorHandling -Source "C:\test.txt" -Destination "C:\Backup\test.txt" -Verbose
Exercise 2: Service Monitor with Error Recovery
Task: Create a service monitoring script that attempts to restart failed services.
Requirements:
- Check if service exists
- Monitor service status
- Attempt restart if stopped
- Retry logic with max attempts
- Detailed logging
Show Solution
function Monitor-ServiceWithRecovery {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ServiceName,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 10
)
try {
# Check if service exists
$service = Get-Service -Name $ServiceName -ErrorAction Stop
Write-Host "Monitoring service: $($service.DisplayName)" -ForegroundColor Cyan
# Check service status
if ($service.Status -eq 'Running') {
Write-Host "Service is running normally" -ForegroundColor Green
return $true
}
Write-Warning "Service is $($service.Status)"
# Attempt recovery
$attempt = 0
while ($attempt -lt $MaxRetries) {
$attempt++
try {
Write-Host "Recovery attempt $attempt of $MaxRetries..." -ForegroundColor Yellow
Start-Service -Name $ServiceName -ErrorAction Stop
# Wait and verify
Start-Sleep -Seconds 5
$service.Refresh()
if ($service.Status -eq 'Running') {
Write-Host "Service started successfully!" -ForegroundColor Green
return $true
}
else {
Write-Warning "Service status: $($service.Status)"
}
}
catch {
Write-Error "Failed to start service: $_"
if ($attempt -lt $MaxRetries) {
Write-Host "Waiting $RetryDelaySeconds seconds before retry..."
Start-Sleep -Seconds $RetryDelaySeconds
}
}
}
# All retries failed
Write-Error "Failed to start service $ServiceName after $MaxRetries attempts"
return $false
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
Write-Error "Service not found: $ServiceName"
return $false
}
catch {
Write-Error "Unexpected error: $_"
return $false
}
}
# Test the function
Monitor-ServiceWithRecovery -ServiceName "Spooler" -Verbose
Exercise 3: Data Validation with Custom Errors
Task: Create a user registration function with comprehensive validation.
Requirements:
- Validate username (alphanumeric, 3-20 chars)
- Validate email format
- Validate age (18-120)
- Custom error messages
- Collect all validation errors
Show Solution
function Register-User {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Username,
[Parameter(Mandatory)]
[string]$Email,
[Parameter(Mandatory)]
[int]$Age
)
$validationErrors = @()
# Validate Username
if ($Username.Length -lt 3 -or $Username.Length -gt 20) {
$validationErrors += "Username must be between 3 and 20 characters"
}
if ($Username -notmatch '^[a-zA-Z0-9]+$') {
$validationErrors += "Username can only contain letters and numbers"
}
# Validate Email
$emailRegex = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if ($Email -notmatch $emailRegex) {
$validationErrors += "Invalid email format"
}
# Validate Age
if ($Age -lt 18) {
$validationErrors += "User must be at least 18 years old"
}
if ($Age -gt 120) {
$validationErrors += "Invalid age: $Age"
}
# Check for validation errors
if ($validationErrors.Count -gt 0) {
$errorMessage = "User registration failed with the following errors:`n"
$errorMessage += ($validationErrors | ForEach-Object { " - $_" }) -join "`n"
Write-Error $errorMessage
return [PSCustomObject]@{
Success = $false
Errors = $validationErrors
User = $null
}
}
# All validations passed
$user = [PSCustomObject]@{
Username = $Username
Email = $Email
Age = $Age
CreatedAt = Get-Date
}
Write-Host "User registered successfully!" -ForegroundColor Green
Write-Host "Username: $Username"
Write-Host "Email: $Email"
Write-Host "Age: $Age"
return [PSCustomObject]@{
Success = $true
Errors = @()
User = $user
}
}
# Test with valid data
$result1 = Register-User -Username "john123" -Email "[email protected]" -Age 25
# Test with invalid data
$result2 = Register-User -Username "ab" -Email "invalid-email" -Age 15
# Check results
if ($result1.Success) {
Write-Host "`nUser 1 created: $($result1.User.Username)" -ForegroundColor Green
}
if (-not $result2.Success) {
Write-Host "`nUser 2 validation errors:" -ForegroundColor Red
$result2.Errors | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
}
10.12 Chapter Summary
- Error Types: Understand terminating vs non-terminating errors
- Try/Catch/Finally: Primary error handling mechanism
- Error Variables: Use $_, $Error, $?, $LASTEXITCODE
- ErrorAction: Control error behavior (Stop, Continue, SilentlyContinue)
- Throw vs Write-Error: Terminating vs non-terminating errors
- Specific Catches: Handle different exception types differently
- Cleanup with Finally: Always execute cleanup code
- Logging: Record errors for troubleshooting
- Retry Logic: Implement for transient failures
- Meaningful Messages: Help users understand what went wrong
10.13 Chapter Quiz
1. What's the difference between terminating and non-terminating errors?
Show Answer
Answer: Terminating errors stop script execution immediately and can be caught with try/catch. Non-terminating errors display an error message but allow the script to continue. You can convert non-terminating errors to terminating errors using -ErrorAction Stop.
2. When should you use the Finally block?
Show Answer
Answer: Use Finally for cleanup operations that must always execute, regardless of whether an error occurred. Common uses include closing file handles, database connections, or disposing of objects. The Finally block executes even if there's a return statement in the try or catch blocks.
3. What does -ErrorAction SilentlyContinue do?
Show Answer
Answer: -ErrorAction SilentlyContinue suppresses error messages and continues script execution. The error is still added to the $Error variable but isn't displayed. Use this when errors are expected and you want to handle them programmatically without showing error messages to users.
4. How do you catch specific exception types?
Show Answer
Answer: Use multiple catch blocks with specific exception types in square brackets: catch [System.IO.FileNotFoundException] { }, followed by a generic catch block for all other exceptions.
5. What's the difference between throw and Write-Error?
Show Answer
Answer: throw creates a terminating error that stops execution and can be caught with try/catch. Write-Error creates a non-terminating error that displays an error message but allows the script to continue. Use throw for critical errors and Write-Error for warnings or non-critical issues.
6. What information does $_ contain in a catch block?
Show Answer
Answer: In a catch block, $_ contains the current error object with properties like Exception.Message, Exception.GetType(), InvocationInfo.ScriptLineNumber, and ScriptStackTrace.
7. How do you check if the last command succeeded?
Show Answer
Answer: Use the $? automatic variable, which contains $true if the last command succeeded and $false if it failed. For external programs, also check $LASTEXITCODE which contains the exit code (0 typically means success).
8. Why use -ErrorAction Stop with try/catch?
Show Answer
Answer: Many cmdlets generate non-terminating errors by default, which cannot be caught with try/catch. Using -ErrorAction Stop converts these to terminating errors that can be caught, allowing you to handle them in your catch block.
9. What's the purpose of $Error[0]?
Show Answer
Answer: $Error is an array that stores all errors from the current session, with the most recent error at index 0. $Error[0] gives you the last error that occurred, useful for debugging or logging. You can clear the error array with $Error.Clear().
10. When should you implement retry logic?
Show Answer
Answer: Implement retry logic for transient failures that might succeed on subsequent attempts, such as network operations, database connections, file operations (file in use), and service dependencies. Always include a maximum retry count and delay between attempts.
Chapter 11 Working with Files and Folders
11.1 Introduction to File System Management
File and folder management is one of the most common tasks in system administration. PowerShell provides powerful cmdlets that go far beyond what CMD offers, with rich filtering, pipeline support, and object-oriented operations.
Get-ChildItem- List files and foldersNew-Item- Create files and foldersCopy-Item- Copy files and foldersMove-Item- Move/rename files and foldersRemove-Item- Delete files and foldersTest-Path- Check if path existsGet-Content- Read file contentSet-Content- Write file content
11.2 Listing Files and Folders (Get-ChildItem)
Get-ChildItem (alias: gci, dir, ls) is the PowerShell equivalent of CMD's dir, but much more powerful.
Basic Listing
# List current directory
Get-ChildItem
# List specific path
Get-ChildItem C:\Windows
# Using alias (like CMD dir)
dir C:\Temp
# List with full details
Get-ChildItem | Format-List *
Filtering Files
# All .txt files
Get-ChildItem *.txt
# Multiple extensions
Get-ChildItem -Include *.txt, *.log
# Exclude certain files
Get-ChildItem -Exclude *.tmp
# Files only (no directories)
Get-ChildItem -File
# Directories only
Get-ChildItem -Directory
Recursive Search
# Search all subdirectories
Get-ChildItem -Recurse
# Find all .log files recursively
Get-ChildItem C:\Windows -Filter *.log -Recurse -ErrorAction SilentlyContinue
# Limit recursion depth
Get-ChildItem -Recurse -Depth 2
# Find large files (over 100MB)
Get-ChildItem -Recurse -File | Where-Object { $_.Length -gt 100MB }
11.3 Creating Files and Folders (New-Item)
Creating Folders
# Create single folder
New-Item -Path "C:\Temp\NewFolder" -ItemType Directory
# Create nested folders (creates parent if needed)
New-Item -Path "C:\Temp\Parent\Child\GrandChild" -ItemType Directory -Force
# Create multiple folders
"Logs", "Data", "Config" | ForEach-Object {
New-Item -Path "C:\MyApp\$_" -ItemType Directory
}
Creating Files
# Create empty file
New-Item -Path "C:\Temp\test.txt" -ItemType File
# Create file with content
New-Item -Path "C:\Temp\config.txt" -ItemType File -Value "Initial content"
# Create and overwrite if exists
New-Item -Path "C:\Temp\log.txt" -ItemType File -Force
11.4 Copying Files and Folders (Copy-Item)
Basic Copy Operations
# Copy single file
Copy-Item -Path "C:\Temp\source.txt" -Destination "C:\Backup\source.txt"
# Copy and rename
Copy-Item -Path "C:\Temp\old.txt" -Destination "C:\Temp\new.txt"
# Copy to directory (keeps same name)
Copy-Item -Path "C:\Temp\file.txt" -Destination "C:\Backup\"
# Overwrite existing file
Copy-Item -Path "C:\Temp\file.txt" -Destination "C:\Backup\file.txt" -Force
Copying Folders
# Copy folder and all contents
Copy-Item -Path "C:\Source" -Destination "C:\Destination" -Recurse
# Copy folder contents only
Copy-Item -Path "C:\Source\*" -Destination "C:\Destination" -Recurse
# Copy with filter
Copy-Item -Path "C:\Logs\*.log" -Destination "C:\Backup\Logs\"
11.5 Moving and Renaming (Move-Item, Rename-Item)
Moving Files
# Move single file
Move-Item -Path "C:\Temp\file.txt" -Destination "C:\Archive\"
# Move and rename
Move-Item -Path "C:\Temp\old.txt" -Destination "C:\Archive\new.txt"
# Move multiple files
Get-ChildItem "C:\Temp\*.log" | Move-Item -Destination "C:\Logs\"
Renaming Files
# Rename single file
Rename-Item -Path "C:\Temp\old.txt" -NewName "new.txt"
# Bulk rename - add prefix
Get-ChildItem "C:\Temp\*.txt" | ForEach-Object {
Rename-Item -Path $_.FullName -NewName "backup_$($_.Name)"
}
# Bulk rename - change extension
Get-ChildItem "C:\Temp\*.txt" | ForEach-Object {
Rename-Item -Path $_.FullName -NewName ($_.BaseName + ".log")
}
11.6 Deleting Files and Folders (Remove-Item)
β οΈ Use with caution!
Deleting Files
# Delete single file
Remove-Item -Path "C:\Temp\file.txt"
# Delete with confirmation
Remove-Item -Path "C:\Temp\file.txt" -Confirm
# Delete without confirmation
Remove-Item -Path "C:\Temp\file.txt" -Force
# Delete files older than 30 days
Get-ChildItem "C:\Logs\*.log" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
Deleting Folders
# Delete empty folder
Remove-Item -Path "C:\Temp\EmptyFolder"
# Delete folder and all contents
Remove-Item -Path "C:\Temp\OldFolder" -Recurse -Force
# Delete folder contents but keep folder
Remove-Item -Path "C:\Temp\MyFolder\*" -Recurse -Force
11.7 Reading and Writing File Content
Reading Files
# Read entire file as array of lines
$content = Get-Content -Path "C:\Temp\file.txt"
# Read as single string
$content = Get-Content -Path "C:\Temp\file.txt" -Raw
# Read first 10 lines
$content = Get-Content -Path "C:\Temp\file.txt" -TotalCount 10
# Read last 20 lines (like tail)
$content = Get-Content -Path "C:\Temp\file.txt" -Tail 20
# Monitor file for changes (like tail -f)
Get-Content -Path "C:\Logs\app.log" -Wait -Tail 10
Writing Files
# Write string to file (overwrites)
Set-Content -Path "C:\Temp\output.txt" -Value "Hello World"
# Write array to file
$lines = @("Line 1", "Line 2", "Line 3")
Set-Content -Path "C:\Temp\output.txt" -Value $lines
# Append to file
Add-Content -Path "C:\Temp\log.txt" -Value "New log entry"
# Write with specific encoding
Set-Content -Path "C:\Temp\file.txt" -Value "Content" -Encoding UTF8
11.8 Testing Paths (Test-Path)
# Check if file exists
if (Test-Path "C:\Temp\file.txt") {
Write-Host "File exists"
}
# Check if folder exists
if (Test-Path "C:\Temp" -PathType Container) {
Write-Host "Folder exists"
}
# Check if item is a file
if (Test-Path "C:\Temp\file.txt" -PathType Leaf) {
Write-Host "It's a file"
}
11.9 Path Manipulation
# Join paths (handles separators correctly)
$path = Join-Path -Path "C:\Temp" -ChildPath "subfolder\file.txt"
# Split path into components
$parent = Split-Path "C:\Temp\file.txt" -Parent # C:\Temp
$leaf = Split-Path "C:\Temp\file.txt" -Leaf # file.txt
# Get file name without extension
$baseName = [System.IO.Path]::GetFileNameWithoutExtension("C:\Temp\file.txt")
# Get extension
$ext = [System.IO.Path]::GetExtension("C:\Temp\file.txt")
11.10 Working with File Properties
# Get file object
$file = Get-Item "C:\Temp\file.txt"
# Access properties
$file.Name # file.txt
$file.FullName # C:\Temp\file.txt
$file.Length # Size in bytes
$file.CreationTime # Creation date
$file.LastWriteTime # Last modified date
$file.Extension # .txt
$file.IsReadOnly # True/False
# Set read-only
$file.IsReadOnly = $true
11.11 Real-World Example: Clean Old Log Files
function Remove-OldLogs {
param(
[Parameter(Mandatory=$true)]
[string]$LogPath,
[int]$DaysToKeep = 30,
[switch]$WhatIf
)
$cutoffDate = (Get-Date).AddDays(-$DaysToKeep)
$oldFiles = Get-ChildItem -Path $LogPath -Filter "*.log" -Recurse |
Where-Object { $_.LastWriteTime -lt $cutoffDate }
if ($oldFiles.Count -eq 0) {
Write-Host "No old log files found" -ForegroundColor Green
return
}
$totalSize = ($oldFiles | Measure-Object -Property Length -Sum).Sum
$sizeGB = [math]::Round($totalSize / 1GB, 2)
Write-Host "Found $($oldFiles.Count) files older than $DaysToKeep days" -ForegroundColor Yellow
Write-Host "Total size: $sizeGB GB" -ForegroundColor Yellow
if ($WhatIf) {
$oldFiles | Select-Object FullName, LastWriteTime | Format-Table
return
}
$confirmation = Read-Host "Delete these files? (yes/no)"
if ($confirmation -eq "yes") {
$oldFiles | Remove-Item -Force
Write-Host "Deleted $($oldFiles.Count) files" -ForegroundColor Green
}
}
# Usage
Remove-OldLogs -LogPath "C:\Logs" -DaysToKeep 30 -WhatIf
11.12 Chapter Summary
- Get-ChildItem - Powerful file listing with filtering and recursion
- New-Item - Create files and folders with -Force for parents
- Copy-Item - Copy files/folders with -Recurse
- Move-Item - Move or rename files and folders
- Remove-Item - Delete files/folders (use -Recurse carefully!)
- Test-Path - Check existence before operations
- Get-Content/Set-Content - Read and write file content
- Path Operations - Use Join-Path, Split-Path for compatibility
- Best Practice - Always test with -WhatIf first!
Chapter 12: Working with Registry
12.1 Introduction to Registry Management
The Windows Registry stores configuration settings for the operating system and applications. PowerShell provides cmdlets to read, modify, and manage registry keys and values safely.
Registry Structure:
- Hives: Top-level containers (HKLM, HKCU, etc.)
- Keys: Similar to folders in a file system
- Values: Similar to files, contain actual data
- Data Types: String, DWORD, Binary, etc.
12.2 Registry Providers
PowerShell exposes the registry through PSDrives, making it accessible like a file system:
# List registry drives
Get-PSDrive -PSProvider Registry
# Output shows:
# HKCU: (HKEY_CURRENT_USER)
# HKLM: (HKEY_LOCAL_MACHINE)
# Change to registry location
Set-Location HKCU:\Software
# List keys (like folders)
Get-ChildItem
# View current location
Get-Location
12.3 Reading Registry Values
# Read all properties from a key
Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
# Read specific property
Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer" -Name "ShellState"
# Read from HKLM (requires admin for some keys)
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "ProductName"
# Get Windows version information
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
$version = Get-ItemProperty -Path $regPath
Write-Host "Product Name: $($version.ProductName)"
Write-Host "Current Build: $($version.CurrentBuild)"
Write-Host "Release ID: $($version.ReleaseId)"
12.4 Creating Registry Keys and Values
# Create a new registry key
New-Item -Path "HKCU:\Software\MyApp" -Force
# Create nested keys
New-Item -Path "HKCU:\Software\MyApp\Settings\Advanced" -Force
# The -Force parameter creates parent keys if they don't exist
# Create a String value
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "AppPath" -Value "C:\MyApp" -PropertyType String
# Create a DWORD value (32-bit integer)
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version" -Value 1 -PropertyType DWord
# Create a Binary value
$binaryData = [byte[]](0x01, 0x02, 0x03, 0x04)
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "BinaryData" -Value $binaryData -PropertyType Binary
# Create a Multi-String value
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Servers" -Value @("Server1", "Server2") -PropertyType MultiString
Registry Value Types:
| Type | Description | Example |
|---|---|---|
| String | Text value | "C:\Program Files" |
| ExpandString | String with environment variables | "%SystemRoot%\System32" |
| DWord | 32-bit integer | 1, 0, 255 |
| QWord | 64-bit integer | Large numbers |
| Binary | Raw binary data | Byte array |
| MultiString | Array of strings | @("Value1", "Value2") |
12.5 Modifying Registry Values
# Modify a string value
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name "AppPath" -Value "D:\MyApp"
# Modify a DWORD value
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version" -Value 2
# Modify multiple values at once
$properties = @{
"AppPath" = "E:\MyApp"
"Version" = 3
}
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name $properties.Keys -Value $properties.Values
# Rename a registry value
Rename-ItemProperty -Path "HKCU:\Software\MyApp" -Name "AppPath" -NewName "ApplicationPath"
12.6 Deleting Registry Keys and Values
# Delete a specific value
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version"
# Delete with confirmation
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version" -Confirm
# Delete a registry key (must be empty or use -Recurse)
Remove-Item -Path "HKCU:\Software\MyApp\Settings"
# Delete key and all subkeys/values
Remove-Item -Path "HKCU:\Software\MyApp" -Recurse -Force
# Safe deletion with WhatIf
Remove-Item -Path "HKCU:\Software\MyApp" -Recurse -WhatIf
12.7 Testing Registry Existence
# Check if registry key exists
if (Test-Path "HKCU:\Software\MyApp") {
Write-Host "MyApp key exists"
} else {
Write-Host "MyApp key does not exist"
}
# Check if specific value exists
$path = "HKCU:\Software\MyApp"
$valueName = "Version"
$property = Get-ItemProperty -Path $path -Name $valueName -ErrorAction SilentlyContinue
if ($property) {
Write-Host "Value exists: $($property.$valueName)"
} else {
Write-Host "Value does not exist"
}
12.8 Backup and Restore Registry
# Export registry key to .reg file
# Using reg.exe command
reg export "HKCU\Software\MyApp" "C:\Backup\MyApp.reg" /y
# PowerShell method - export to XML
$regPath = "HKCU:\Software\MyApp"
$backupFile = "C:\Backup\MyApp.xml"
if (Test-Path $regPath) {
Get-ItemProperty -Path $regPath | Export-Clixml -Path $backupFile
Write-Host "Registry backed up to $backupFile"
}
# Import from .reg file
reg import "C:\Backup\MyApp.reg"
# PowerShell method - restore from XML
$backupFile = "C:\Backup\MyApp.xml"
$regPath = "HKCU:\Software\MyApp"
if (Test-Path $backupFile) {
$backup = Import-Clixml -Path $backupFile
# Recreate key if needed
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force
}
# Restore each property
$backup.PSObject.Properties | ForEach-Object {
if ($_.Name -notin @('PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider')) {
Set-ItemProperty -Path $regPath -Name $_.Name -Value $_.Value
}
}
Write-Host "Registry restored from $backupFile"
}
12.9 Real-World Examples
function Test-SoftwareInstalled {
param(
[string]$SoftwareName
)
# Check both 32-bit and 64-bit registry locations
$paths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
foreach ($path in $paths) {
$installed = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "*$SoftwareName*" }
if ($installed) {
Write-Host "Found: $($installed.DisplayName)"
Write-Host "Version: $($installed.DisplayVersion)"
Write-Host "Publisher: $($installed.Publisher)"
return $true
}
}
Write-Host "$SoftwareName not found"
return $false
}
# Usage
Test-SoftwareInstalled -SoftwareName "Google Chrome"
function Set-AppConfiguration {
param(
[string]$AppName,
[hashtable]$Settings
)
$regPath = "HKCU:\Software\$AppName"
# Create key if it doesn't exist
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
Write-Host "Created registry key: $regPath"
}
# Set each setting
foreach ($key in $Settings.Keys) {
$value = $Settings[$key]
# Determine type
$type = switch ($value.GetType().Name) {
"Int32" { "DWord" }
"String" { "String" }
"String[]" { "MultiString" }
default { "String" }
}
# Check if property exists
$existing = Get-ItemProperty -Path $regPath -Name $key -ErrorAction SilentlyContinue
if ($existing) {
Set-ItemProperty -Path $regPath -Name $key -Value $value
Write-Host "Updated: $key = $value"
} else {
New-ItemProperty -Path $regPath -Name $key -Value $value -PropertyType $type | Out-Null
Write-Host "Created: $key = $value"
}
}
}
# Usage
$config = @{
"ServerURL" = "https://api.example.com"
"Timeout" = 30
"EnableLogging" = 1
"AllowedUsers" = @("User1", "User2", "User3")
}
Set-AppConfiguration -AppName "MyApp" -Settings $config
function Get-RegistrySnapshot {
param(
[string]$Path
)
$snapshot = @{}
if (Test-Path $Path) {
# Get all properties
$properties = Get-ItemProperty -Path $Path
foreach ($prop in $properties.PSObject.Properties) {
if ($prop.Name -notin @('PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider')) {
$snapshot[$prop.Name] = $prop.Value
}
}
# Get all subkeys
$subkeys = Get-ChildItem -Path $Path -ErrorAction SilentlyContinue
foreach ($subkey in $subkeys) {
$snapshot[$subkey.PSChildName] = Get-RegistrySnapshot -Path $subkey.PSPath
}
}
return $snapshot
}
function Compare-RegistrySnapshots {
param(
[hashtable]$Before,
[hashtable]$After
)
$changes = @()
# Check for new or modified values
foreach ($key in $After.Keys) {
if (-not $Before.ContainsKey($key)) {
$changes += "ADDED: $key = $($After[$key])"
} elseif ($Before[$key] -ne $After[$key]) {
$changes += "MODIFIED: $key = $($Before[$key]) -> $($After[$key])"
}
}
# Check for deleted values
foreach ($key in $Before.Keys) {
if (-not $After.ContainsKey($key)) {
$changes += "DELETED: $key"
}
}
return $changes
}
# Usage
$path = "HKCU:\Software\MyApp"
$before = Get-RegistrySnapshot -Path $path
# ... make changes ...
$after = Get-RegistrySnapshot -Path $path
$changes = Compare-RegistrySnapshots -Before $before -After $after
if ($changes.Count -gt 0) {
Write-Host "Registry changes detected:"
$changes | ForEach-Object { Write-Host " $_" }
} else {
Write-Host "No changes detected"
}
12.10 Best Practices for Registry Management
β Registry Best Practices:
- Always Backup: Export keys before making changes
- Test First: Use -WhatIf parameter when available
- Use Try/Catch: Wrap registry operations in error handling
- Check Existence: Use Test-Path before accessing keys
- Use Full Paths: Always specify complete registry paths
- Avoid HKLM Changes: Modify HKCU when possible (no admin required)
- Document Changes: Keep a log of what you modify
- Use Correct Types: Specify proper PropertyType for values
- Handle Errors: Use -ErrorAction SilentlyContinue with proper checks
- Restart if Needed: Some registry changes require logout/restart
function Set-RegistryValueSafely {
param(
[string]$Path,
[string]$Name,
$Value,
[string]$Type = "String"
)
try {
# Backup first
$backupPath = "$env:TEMP\RegBackup_$(Get-Date -Format 'yyyyMMdd_HHmmss').xml"
if (Test-Path $Path) {
Get-ItemProperty -Path $Path | Export-Clixml -Path $backupPath
Write-Host "Backup created: $backupPath"
}
# Create key if needed
if (-not (Test-Path $Path)) {
New-Item -Path $Path -Force | Out-Null
}
# Check if value exists
$existing = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
if ($existing) {
# Modify existing
Set-ItemProperty -Path $Path -Name $Name -Value $Value
Write-Host "β Modified: $Name = $Value"
} else {
# Create new
New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType $Type | Out-Null
Write-Host "β Created: $Name = $Value"
}
return $true
}
catch {
Write-Error "Failed to set registry value: $_"
Write-Host "Backup available at: $backupPath"
return $false
}
}
# Usage
Set-RegistryValueSafely -Path "HKCU:\Software\MyApp" -Name "Setting1" -Value "Value1" -Type "String"
12.11 Common Registry Locations
Frequently Used Registry Paths:
| Purpose | Registry Path |
|---|---|
| Installed Software (64-bit) | HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
| Installed Software (32-bit) | HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
| Startup Programs (User) | HKCU:\Software\Microsoft\Windows\CurrentVersion\Run |
| Startup Programs (System) | HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run |
| Environment Variables (User) | HKCU:\Environment |
| Environment Variables (System) | HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment |
| Windows Version | HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion |
| User Shell Folders | HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders |
12.12 Chapter Summary
Key Takeaways:
- β Registry is accessible through HKCU: and HKLM: PSDrives
- β Use Get-ItemProperty to read registry values
- β Use New-Item to create registry keys
- β Use New-ItemProperty to create registry values
- β Use Set-ItemProperty to modify existing values
- β Use Remove-Item/Remove-ItemProperty to delete keys/values
- β Always backup before making changes
- β Use proper data types (String, DWord, Binary, etc.)
- β Test with -WhatIf and use error handling
- β HKCU changes don't require admin, HKLM changes do
Chapter 13: Working with Services and Processes
13.1 Introduction to Services and Processes
Services and processes are fundamental components of Windows system management. Understanding how to monitor, control, and troubleshoot them is essential for any system administrator.
Services vs Processes:
| Aspect | Services | Processes |
|---|---|---|
| Definition | Background applications that run without user interaction | Running instances of applications or programs |
| Startup | Can start automatically at boot | Started by user or system |
| User Interface | No UI (background only) | May have UI or run in background |
| Management | Managed through Services console | Managed through Task Manager |
| Examples | Windows Update, Print Spooler | chrome.exe, powershell.exe |
13.2 Working with Services
13.2.1 Getting Service Information
# Get all services
Get-Service
# Get specific service
Get-Service -Name "Spooler"
# Get services by display name pattern
Get-Service -DisplayName "*Windows*"
# Get multiple services
Get-Service -Name "Spooler", "W32Time", "WinRM"
# Get only running services
Get-Service | Where-Object { $_.Status -eq "Running" }
# Get stopped services
Get-Service | Where-Object { $_.Status -eq "Stopped" }
# Count services by status
Get-Service | Group-Object Status | Select-Object Name, Count
# Get services that can be stopped
Get-Service | Where-Object { $_.CanStop -eq $true }
# Get service with all properties
Get-Service -Name "Spooler" | Select-Object *
# Get service startup type using WMI
Get-WmiObject Win32_Service -Filter "Name='Spooler'" |
Select-Object Name, DisplayName, State, StartMode, PathName
# Get service dependencies
$service = Get-Service -Name "Spooler"
Write-Host "Services that $($service.Name) depends on:"
$service.ServicesDependedOn
Write-Host "`nServices that depend on $($service.Name):"
$service.DependentServices
13.2.2 Starting and Stopping Services
# Start a service
Start-Service -Name "Spooler"
# Start service with confirmation
Start-Service -Name "Spooler" -Confirm
# Start service and display status
Start-Service -Name "Spooler" -PassThru
# Start multiple services
Start-Service -Name "Spooler", "W32Time"
# Stop a service
Stop-Service -Name "Spooler"
# Stop service and dependent services
Stop-Service -Name "Spooler" -Force
# Stop with WhatIf (test without executing)
Stop-Service -Name "Spooler" -WhatIf
# Stop service with timeout
Stop-Service -Name "Spooler" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5
if ((Get-Service -Name "Spooler").Status -eq "Running") {
Write-Host "Service did not stop within timeout"
}
# Restart a service
Restart-Service -Name "Spooler"
# Restart with force (stops dependent services)
Restart-Service -Name "Spooler" -Force
# Restart and display result
Restart-Service -Name "Spooler" -PassThru | Select-Object Name, Status
13.2.3 Configuring Services
# Change service startup type to Automatic
Set-Service -Name "Spooler" -StartupType Automatic
# Change to Manual
Set-Service -Name "Spooler" -StartupType Manual
# Disable service
Set-Service -Name "Spooler" -StartupType Disabled
# Change display name and description
Set-Service -Name "Spooler" -DisplayName "Print Spooler Service" -Description "Manages print jobs"
# Change service status and startup type
Set-Service -Name "Spooler" -Status Running -StartupType Automatic
# Startup types available:
# - Automatic: Starts at boot
# - AutomaticDelayedStart: Starts shortly after boot
# - Manual: Starts on demand
# - Disabled: Cannot be started
# Set to Automatic (Delayed Start)
Set-Service -Name "MyService" -StartupType Automatic
$service = Get-WmiObject Win32_Service -Filter "Name='MyService'"
$service.ChangeStartMode("Automatic Delayed Start")
# Verify startup type
Get-WmiObject Win32_Service -Filter "Name='MyService'" | Select-Object Name, StartMode
13.2.4 Service Dependencies
function Get-ServiceDependencies {
param([string]$ServiceName)
$service = Get-Service -Name $ServiceName
Write-Host "=== Service: $($service.DisplayName) ===" -ForegroundColor Cyan
Write-Host "Status: $($service.Status)"
Write-Host ""
Write-Host "Services this service depends on:" -ForegroundColor Yellow
if ($service.ServicesDependedOn.Count -eq 0) {
Write-Host " None"
} else {
$service.ServicesDependedOn | ForEach-Object {
Write-Host " - $($_.DisplayName) ($($_.Name)) - Status: $($_.Status)"
}
}
Write-Host ""
Write-Host "Services that depend on this service:" -ForegroundColor Yellow
if ($service.DependentServices.Count -eq 0) {
Write-Host " None"
} else {
$service.DependentServices | ForEach-Object {
Write-Host " - $($_.DisplayName) ($($_.Name)) - Status: $($_.Status)"
}
}
}
# Usage
Get-ServiceDependencies -ServiceName "Spooler"
13.3 Working with Processes
13.3.1 Getting Process Information
# Get all processes
Get-Process
# Get specific process by name
Get-Process -Name "powershell"
# Get process by ID
Get-Process -Id 1234
# Get multiple processes
Get-Process -Name "chrome", "firefox", "edge"
# Get processes with wildcard
Get-Process -Name "power*"
# Get top 10 processes by CPU usage
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU, Id
# Get top 10 processes by memory usage
Get-Process | Sort-Object WorkingSet -Descending |
Select-Object -First 10 Name, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet/1MB,2)}}, Id
# Get processes using more than 100MB
Get-Process | Where-Object { $_.WorkingSet -gt 100MB } |
Select-Object Name, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet/1MB,2)}}
# Get processes by company
Get-Process | Where-Object { $_.Company -like "*Microsoft*" } |
Select-Object Name, Company, Path
# Get all properties of a process
Get-Process -Name "powershell" | Select-Object *
# Get process with specific properties
Get-Process | Select-Object Name, Id, CPU,
@{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet/1MB,2)}},
@{Name="Threads";Expression={$_.Threads.Count}},
StartTime,
Path
# Get process modules (DLLs loaded)
Get-Process -Name "powershell" | Select-Object -ExpandProperty Modules |
Select-Object ModuleName, FileName
# Get process threads
(Get-Process -Name "powershell").Threads | Select-Object Id, ThreadState, StartTime
13.3.2 Starting Processes
# Start notepad
Start-Process notepad
# Start process with arguments
Start-Process notepad -ArgumentList "C: empile.txt"
# Start process and wait for it to finish
Start-Process notepad -Wait
# Start process in new window
Start-Process powershell -ArgumentList "-NoExit", "-Command", "Get-Process"
# Start process as administrator
Start-Process powershell -Verb RunAs
# Start process with working directory
Start-Process notepad -WorkingDirectory "C: emp"
# Start process and capture the process object
$proc = Start-Process notepad -PassThru
Write-Host "Started process ID: $($proc.Id)"
# Start process and redirect output
Start-Process ping -ArgumentList "google.com" -RedirectStandardOutput "C: emp\ping_output.txt" -NoNewWindow -Wait
# Read the output
Get-Content "C: emp\ping_output.txt"
# Start process with input/output redirection
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "cmd.exe"
$psi.Arguments = "/c dir C:"
$psi.RedirectStandardOutput = $true
$psi.UseShellExecute = $false
$psi.CreateNoWindow = $true
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Null
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()
Write-Host $output
13.3.3 Stopping Processes
# Stop process by name
Stop-Process -Name "notepad"
# Stop process by ID
Stop-Process -Id 1234
# Stop process forcefully
Stop-Process -Name "notepad" -Force
# Stop multiple processes
Stop-Process -Name "notepad", "calc"
# Stop with confirmation
Stop-Process -Name "notepad" -Confirm
# Stop all instances of a process
Get-Process -Name "notepad" | Stop-Process
# Stop with WhatIf (test without executing)
Stop-Process -Name "notepad" -WhatIf
function Stop-ProcessGracefully {
param(
[string]$ProcessName,
[int]$TimeoutSeconds = 10
)
$processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
if ($processes) {
foreach ($proc in $processes) {
Write-Host "Attempting to close $ProcessName (PID: $($proc.Id))..."
# Try to close gracefully first
$proc.CloseMainWindow() | Out-Null
# Wait for process to exit
$proc.WaitForExit($TimeoutSeconds * 1000)
# Check if still running
if (-not $proc.HasExited) {
Write-Host "Process did not exit gracefully. Forcing termination..."
Stop-Process -Id $proc.Id -Force
} else {
Write-Host "Process closed successfully."
}
}
} else {
Write-Host "Process $ProcessName not found."
}
}
# Usage
Stop-ProcessGracefully -ProcessName "notepad" -TimeoutSeconds 5
13.3.4 Monitoring Processes
function Watch-ProcessResources {
param(
[string]$ProcessName,
[int]$IntervalSeconds = 5
)
Write-Host "Monitoring $ProcessName (Press Ctrl+C to stop)..." -ForegroundColor Cyan
Write-Host ""
while ($true) {
$processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
if ($processes) {
Clear-Host
Write-Host "=== Process Monitor: $ProcessName ===" -ForegroundColor Cyan
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host ""
$processes | ForEach-Object {
$memoryMB = [math]::Round($_.WorkingSet / 1MB, 2)
$cpuPercent = [math]::Round($_.CPU, 2)
Write-Host "PID: $($_.Id)" -ForegroundColor Yellow
Write-Host " CPU: $cpuPercent seconds"
Write-Host " Memory: $memoryMB MB"
Write-Host " Threads: $($_.Threads.Count)"
Write-Host " Handles: $($_.HandleCount)"
Write-Host ""
}
} else {
Write-Host "Process $ProcessName not running." -ForegroundColor Red
}
Start-Sleep -Seconds $IntervalSeconds
}
}
# Usage
# Watch-ProcessResources -ProcessName "chrome" -IntervalSeconds 3
13.4 Real-World Examples
function Monitor-CriticalServices {
param(
[string[]]$ServiceNames,
[string]$LogFile = "C:\Logs\ServiceMonitor.log"
)
# Ensure log directory exists
$logDir = Split-Path $LogFile -Parent
if (-not (Test-Path $logDir)) {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
}
foreach ($serviceName in $ServiceNames) {
try {
$service = Get-Service -Name $serviceName -ErrorAction Stop
if ($service.Status -ne "Running") {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - WARNING: $serviceName is $($service.Status)"
Write-Host $message -ForegroundColor Yellow
Add-Content -Path $LogFile -Value $message
# Attempt to start the service
Write-Host "Attempting to start $serviceName..."
Start-Service -Name $serviceName -ErrorAction Stop
# Wait and verify
Start-Sleep -Seconds 5
$service = Get-Service -Name $serviceName
if ($service.Status -eq "Running") {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - SUCCESS: $serviceName started successfully"
Write-Host $message -ForegroundColor Green
Add-Content -Path $LogFile -Value $message
} else {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - ERROR: Failed to start $serviceName"
Write-Host $message -ForegroundColor Red
Add-Content -Path $LogFile -Value $message
}
} else {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - OK: $serviceName is running"
Write-Host $message -ForegroundColor Green
}
}
catch {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - ERROR: $serviceName - $_"
Write-Host $message -ForegroundColor Red
Add-Content -Path $LogFile -Value $message
}
}
}
# Usage
$criticalServices = @("Spooler", "W32Time", "WinRM")
Monitor-CriticalServices -ServiceNames $criticalServices
function Monitor-ProcessResources {
param(
[int]$MemoryThresholdMB = 500,
[int]$CPUThresholdPercent = 80,
[string]$LogFile = "C:\Logs\ProcessMonitor.log"
)
$processes = Get-Process | Where-Object { $_.WorkingSet -gt 10MB }
foreach ($proc in $processes) {
$memoryMB = [math]::Round($proc.WorkingSet / 1MB, 2)
# Check memory threshold
if ($memoryMB -gt $MemoryThresholdMB) {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - HIGH MEMORY: $($proc.Name) (PID: $($proc.Id)) using $memoryMB MB"
Write-Host $message -ForegroundColor Red
Add-Content -Path $LogFile -Value $message
}
# Check CPU (requires multiple samples for accuracy)
# This is a simplified version
if ($proc.CPU -gt 60) {
$message = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - HIGH CPU: $($proc.Name) (PID: $($proc.Id)) CPU time: $($proc.CPU) seconds"
Write-Host $message -ForegroundColor Yellow
Add-Content -Path $LogFile -Value $message
}
}
# Summary
$totalMemoryMB = [math]::Round((Get-Process | Measure-Object WorkingSet -Sum).Sum / 1MB, 2)
Write-Host "`nTotal Memory Used: $totalMemoryMB MB" -ForegroundColor Cyan
}
# Usage
Monitor-ProcessResources -MemoryThresholdMB 300
function Export-ServiceReport {
param(
[string]$OutputPath = "C:\Reports\ServiceReport.html"
)
# Get all services
$services = Get-Service | Sort-Object DisplayName
# Create HTML report
$html = @"
Service Status Report
Service Status Report
Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Computer: $env:COMPUTERNAME
Summary
Total Services: $($services.Count)
Running: $(($services | Where-Object {$_.Status -eq 'Running'}).Count)
Stopped: $(($services | Where-Object {$_.Status -eq 'Stopped'}).Count)
Display Name
Name
Status
Startup Type
"@
foreach ($service in $services) {
$wmiService = Get-WmiObject Win32_Service -Filter "Name='$($service.Name)'"
$statusClass = if ($service.Status -eq "Running") { "running" } else { "stopped" }
$html += @"
$($service.DisplayName)
$($service.Name)
$($service.Status)
$($wmiService.StartMode)
"@
}
$html += @"
"@
# Ensure directory exists
$reportDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $reportDir)) {
New-Item -ItemType Directory -Path $reportDir -Force | Out-Null
}
# Save report
$html | Out-File -FilePath $OutputPath -Encoding UTF8
Write-Host "Report saved to: $OutputPath" -ForegroundColor Green
# Open in browser
Start-Process $OutputPath
}
# Usage
Export-ServiceReport
function Stop-UnresponsiveProcess {
param(
[string]$ProcessName,
[int]$TimeoutSeconds = 30
)
$processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
if (-not $processes) {
Write-Host "No processes found with name: $ProcessName" -ForegroundColor Yellow
return
}
foreach ($proc in $processes) {
Write-Host "Checking process: $($proc.Name) (PID: $($proc.Id))" -ForegroundColor Cyan
# Check if process is responding
if ($proc.Responding) {
Write-Host " Process is responding. Attempting graceful close..." -ForegroundColor Green
# Try to close main window
$closed = $proc.CloseMainWindow()
if ($closed) {
Write-Host " Close signal sent. Waiting for exit..."
$exited = $proc.WaitForExit($TimeoutSeconds * 1000)
if ($exited) {
Write-Host " Process exited successfully." -ForegroundColor Green
} else {
Write-Host " Process did not exit within timeout. Forcing termination..." -ForegroundColor Yellow
Stop-Process -Id $proc.Id -Force
Write-Host " Process terminated forcefully." -ForegroundColor Red
}
} else {
Write-Host " Could not close main window. Forcing termination..." -ForegroundColor Yellow
Stop-Process -Id $proc.Id -Force
Write-Host " Process terminated forcefully." -ForegroundColor Red
}
} else {
Write-Host " Process is NOT responding. Forcing termination..." -ForegroundColor Red
Stop-Process -Id $proc.Id -Force
Write-Host " Process terminated forcefully." -ForegroundColor Red
}
}
}
# Usage
Stop-UnresponsiveProcess -ProcessName "notepad" -TimeoutSeconds 10
13.5 Advanced Filtering and Queries
# Get automatic services that are stopped
Get-WmiObject Win32_Service |
Where-Object { $_.StartMode -eq "Auto" -and $_.State -eq "Stopped" } |
Select-Object Name, DisplayName, State, StartMode
# Get services running under specific account
Get-WmiObject Win32_Service |
Where-Object { $_.StartName -like "*LocalSystem*" } |
Select-Object Name, DisplayName, StartName
# Get services with specific path
Get-WmiObject Win32_Service |
Where-Object { $_.PathName -like "*system32*" } |
Select-Object Name, DisplayName, PathName
# Get services that can be paused
Get-Service | Where-Object { $_.CanPauseAndContinue } |
Select-Object Name, DisplayName, Status
# Get processes started in the last hour
$oneHourAgo = (Get-Date).AddHours(-1)
Get-Process | Where-Object { $_.StartTime -gt $oneHourAgo } |
Select-Object Name, Id, StartTime, Path
# Get processes with no window title (background processes)
Get-Process | Where-Object { $_.MainWindowTitle -eq "" } |
Select-Object Name, Id, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet/1MB,2)}}
# Get processes by file version
Get-Process | Where-Object { $_.FileVersion -like "10.*" } |
Select-Object Name, FileVersion, Company, Path
# Get child processes of a parent
$parentPID = (Get-Process -Name "powershell")[0].Id
Get-WmiObject Win32_Process | Where-Object { $_.ParentProcessId -eq $parentPID } |
Select-Object ProcessId, Name, CommandLine
13.6 Best Practices
β Service Management Best Practices:
- Check Dependencies: Always verify service dependencies before stopping
- Use -WhatIf: Test commands before executing them
- Error Handling: Use try/catch blocks for service operations
- Logging: Keep logs of service changes
- Startup Type: Be cautious when changing service startup types
- Test in Non-Production: Test service changes in dev environment first
- Document Changes: Keep records of why services were modified
- Use Credentials Carefully: When changing service accounts, ensure proper permissions
β Process Management Best Practices:
- Graceful Shutdown: Try CloseMainWindow() before Stop-Process -Force
- Check Responding: Verify if process is responding before terminating
- Save Work: Warn users before killing processes
- Monitor Resources: Track CPU and memory before taking action
- Use Timeouts: Set reasonable timeouts for process operations
- Handle Errors: Use -ErrorAction and try/catch
- Avoid System Processes: Never kill critical system processes
- Log Actions: Keep audit trail of process terminations
function Manage-ServiceSafely {
param(
[string]$ServiceName,
[ValidateSet("Start", "Stop", "Restart")]
[string]$Action
)
try {
# Check if service exists
$service = Get-Service -Name $ServiceName -ErrorAction Stop
Write-Host "Current Status: $($service.Status)" -ForegroundColor Cyan
# Check dependencies if stopping
if ($Action -eq "Stop") {
$dependents = $service.DependentServices | Where-Object { $_.Status -eq "Running" }
if ($dependents) {
Write-Host "WARNING: The following services depend on $ServiceName and are running:" -ForegroundColor Yellow
$dependents | ForEach-Object { Write-Host " - $($_.DisplayName)" }
$confirm = Read-Host "Continue? (Y/N)"
if ($confirm -ne "Y") {
Write-Host "Operation cancelled."
return
}
}
}
# Perform action
switch ($Action) {
"Start" {
if ($service.Status -eq "Running") {
Write-Host "Service is already running." -ForegroundColor Green
} else {
Start-Service -Name $ServiceName
Write-Host "Service started successfully." -ForegroundColor Green
}
}
"Stop" {
if ($service.Status -eq "Stopped") {
Write-Host "Service is already stopped." -ForegroundColor Green
} else {
Stop-Service -Name $ServiceName -Force
Write-Host "Service stopped successfully." -ForegroundColor Green
}
}
"Restart" {
Restart-Service -Name $ServiceName -Force
Write-Host "Service restarted successfully." -ForegroundColor Green
}
}
# Verify final status
$service = Get-Service -Name $ServiceName
Write-Host "Final Status: $($service.Status)" -ForegroundColor Cyan
}
catch {
Write-Error "Failed to manage service: $_"
}
}
# Usage
Manage-ServiceSafely -ServiceName "Spooler" -Action "Restart"
13.7 Performance Monitoring
function Show-PerformanceDashboard {
param([int]$RefreshSeconds = 5)
while ($true) {
Clear-Host
Write-Host "=== SYSTEM PERFORMANCE DASHBOARD ===" -ForegroundColor Cyan
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host "Computer: $env:COMPUTERNAME"
Write-Host ""
# CPU and Memory
$cpu = Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average
$os = Get-WmiObject Win32_OperatingSystem
$totalMemGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeMemGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedMemGB = $totalMemGB - $freeMemGB
$memPercent = [math]::Round(($usedMemGB / $totalMemGB) * 100, 2)
Write-Host "SYSTEM RESOURCES:" -ForegroundColor Yellow
Write-Host " CPU Usage: $($cpu.Average)%"
Write-Host " Memory: $usedMemGB GB / $totalMemGB GB ($memPercent%)"
Write-Host ""
# Top 5 CPU processes
Write-Host "TOP 5 CPU PROCESSES:" -ForegroundColor Yellow
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | ForEach-Object {
Write-Host " $($_.Name.PadRight(30)) CPU: $([math]::Round($_.CPU, 2)) sec"
}
Write-Host ""
# Top 5 Memory processes
Write-Host "TOP 5 MEMORY PROCESSES:" -ForegroundColor Yellow
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 | ForEach-Object {
$memMB = [math]::Round($_.WorkingSet / 1MB, 2)
Write-Host " $($_.Name.PadRight(30)) Memory: $memMB MB"
}
Write-Host ""
# Service status
$services = Get-Service
$runningCount = ($services | Where-Object { $_.Status -eq "Running" }).Count
$stoppedCount = ($services | Where-Object { $_.Status -eq "Stopped" }).Count
Write-Host "SERVICES:" -ForegroundColor Yellow
Write-Host " Running: $runningCount"
Write-Host " Stopped: $stoppedCount"
Write-Host ""
Write-Host "Press Ctrl+C to exit..." -ForegroundColor Gray
Start-Sleep -Seconds $RefreshSeconds
}
}
# Usage
# Show-PerformanceDashboard -RefreshSeconds 3
13.8 Remote Service and Process Management
# Get service from remote computer
Get-Service -Name "Spooler" -ComputerName "Server01"
# Get multiple services from remote computer
Get-Service -ComputerName "Server01" | Where-Object { $_.Status -eq "Running" }
# Using WMI for remote service management
$service = Get-WmiObject Win32_Service -ComputerName "Server01" -Filter "Name='Spooler'"
$service | Select-Object Name, State, StartMode
# Start service remotely
(Get-WmiObject Win32_Service -ComputerName "Server01" -Filter "Name='Spooler'").StartService()
# Stop service remotely
(Get-WmiObject Win32_Service -ComputerName "Server01" -Filter "Name='Spooler'").StopService()
# Change startup type remotely
$service = Get-WmiObject Win32_Service -ComputerName "Server01" -Filter "Name='Spooler'"
$service.ChangeStartMode("Automatic")
# Get processes from remote computer using WMI
Get-WmiObject Win32_Process -ComputerName "Server01" |
Select-Object ProcessId, Name, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSetSize/1MB,2)}}
# Get specific process remotely
Get-WmiObject Win32_Process -ComputerName "Server01" -Filter "Name='notepad.exe'"
# Start process remotely
Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "notepad.exe" -ComputerName "Server01"
# Stop process remotely
$process = Get-WmiObject Win32_Process -ComputerName "Server01" -Filter "Name='notepad.exe'"
$process.Terminate()
# Using PowerShell Remoting (more powerful)
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 10
}
13.9 Chapter Summary
Key Takeaways:
- β Services are background applications managed with Get-Service, Start-Service, Stop-Service, Restart-Service
- β Processes are running program instances managed with Get-Process, Start-Process, Stop-Process
- β Use Get-Service to list and filter services by status, name, or display name
- β Use Set-Service to change service startup type and configuration
- β Check service dependencies before stopping services
- β Use Get-Process to monitor CPU, memory, and thread usage
- β Always try graceful shutdown (CloseMainWindow) before forcing termination
- β Use -WhatIf parameter to test commands safely
- β Implement error handling with try/catch blocks
- β WMI/CIM provides additional properties and remote management capabilities
- β Monitor resources regularly to prevent performance issues
- β Keep logs of service and process management actions
Chapter 14: Working with WMI and CIM
14.1 Introduction to WMI and CIM
Windows Management Instrumentation (WMI) and Common Information Model (CIM) provide access to system information and management capabilities. CIM is the newer, standards-based approach that works across platforms.
WMI vs CIM Comparison:
| Feature | WMI (Legacy) | CIM (Modern) |
|---|---|---|
| Protocol | DCOM | WS-MAN (WinRM) |
| Cmdlet Prefix | Get-WmiObject | Get-CimInstance |
| Performance | Slower | Faster |
| Remoting | Complex | Built-in with sessions |
| Cross-Platform | Windows only | Standards-based |
| Recommendation | β Deprecated | β Use for new scripts |
14.2 Getting System Information with CIM
# Get operating system information
Get-CimInstance -ClassName Win32_OperatingSystem
# Get computer system information
Get-CimInstance -ClassName Win32_ComputerSystem
# Get BIOS information
Get-CimInstance -ClassName Win32_BIOS
# Get processor information
Get-CimInstance -ClassName Win32_Processor
# Filter using -Filter parameter (server-side filtering - faster)
Get-CimInstance -ClassName Win32_Service -Filter "State = 'Running'"
# Filter stopped services
Get-CimInstance -ClassName Win32_Service -Filter "State = 'Stopped' AND StartMode = 'Auto'"
# Get specific process by name
Get-CimInstance -ClassName Win32_Process -Filter "Name = 'notepad.exe'"
# Filter using Where-Object (client-side filtering)
Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object {$_.DriveType -eq 3}
# Select specific properties
Get-CimInstance -ClassName Win32_OperatingSystem |
Select-Object Caption, Version, BuildNumber, OSArchitecture, LastBootUpTime
# Get disk information with selected properties
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" |
Select-Object DeviceID, VolumeName,
@{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}},
@{Name="FreeGB";Expression={[math]::Round($_.FreeSpace/1GB,2)}},
@{Name="UsedPercent";Expression={[math]::Round((($_.Size-$_.FreeSpace)/$_.Size)*100,2)}}
14.3 Common CIM Classes
Frequently Used CIM Classes:
| Category | Class Name | Purpose |
|---|---|---|
| Hardware | Win32_Processor | CPU information |
| Win32_PhysicalMemory | RAM modules | |
| Win32_DiskDrive | Physical disk drives | |
| Win32_NetworkAdapter | Network adapters | |
| Software | Win32_OperatingSystem | OS information |
| Win32_Product | Installed software (slow!) | |
| Win32_Service | Windows services | |
| Win32_Process | Running processes | |
| System | Win32_ComputerSystem | Computer details |
| Win32_BIOS | BIOS information | |
| Win32_LogicalDisk | Disk volumes |
# Get CPU details
Get-CimInstance -ClassName Win32_Processor |
Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed
# Get RAM information
Get-CimInstance -ClassName Win32_PhysicalMemory |
Select-Object Manufacturer, Capacity, Speed, DeviceLocator |
Format-Table -AutoSize
# Get disk drive information
Get-CimInstance -ClassName Win32_DiskDrive |
Select-Object Model, SerialNumber,
@{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}},
InterfaceType
# Get network adapter configuration
Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = True" |
Select-Object Description, IPAddress, IPSubnet, DefaultIPGateway, DNSServerSearchOrder
14.4 Working with CIM Sessions
# Create a CIM session to local computer
$session = New-CimSession
# Create a CIM session to remote computer
$session = New-CimSession -ComputerName "Server01"
# Create session with credentials
$cred = Get-Credential
$session = New-CimSession -ComputerName "Server01" -Credential $cred
# Use the session for queries
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session
# Close the session when done
Remove-CimSession -CimSession $session
# Create sessions to multiple computers
$computers = "Server01", "Server02", "Server03"
$sessions = New-CimSession -ComputerName $computers
# Query all computers at once
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $sessions
# Display results
$osInfo | Select-Object PSComputerName, Caption, Version, LastBootUpTime | Format-Table -AutoSize
# Clean up sessions
$sessions | Remove-CimSession
14.5 Invoking CIM Methods
# Get all methods for a class
Get-CimClass -ClassName Win32_Process | Select-Object -ExpandProperty CimClassMethods
# Get methods for Win32_Service
Get-CimClass -ClassName Win32_Service | Select-Object -ExpandProperty CimClassMethods
# View method details
$class = Get-CimClass -ClassName Win32_Process
$class.CimClassMethods["Create"]
# Start a process using CIM
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
CommandLine = "notepad.exe"
}
# Stop a process
$process = Get-CimInstance -ClassName Win32_Process -Filter "Name = 'notepad.exe'"
Invoke-CimMethod -InputObject $process -MethodName Terminate
# Restart a service
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name = 'Spooler'"
Invoke-CimMethod -InputObject $service -MethodName StopService
Start-Sleep -Seconds 2
Invoke-CimMethod -InputObject $service -MethodName StartService
14.6 Legacy WMI Cmdlets (For Reference)
WMI to CIM Cmdlet Conversion:
| WMI Cmdlet (Old) | CIM Cmdlet (New) | Notes |
|---|---|---|
| Get-WmiObject | Get-CimInstance | Query WMI/CIM classes |
| Invoke-WmiMethod | Invoke-CimMethod | Execute methods |
| Register-WmiEvent | Register-CimIndicationEvent | Subscribe to events |
| Remove-WmiObject | Remove-CimInstance | Delete instances |
| Set-WmiInstance | Set-CimInstance | Modify instances |
# OLD WAY (WMI) - Don't use for new scripts
Get-WmiObject -Class Win32_OperatingSystem
Get-WmiObject -Class Win32_Service -Filter "State = 'Running'"
Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "notepad.exe"
# NEW WAY (CIM) - Use this instead
Get-CimInstance -ClassName Win32_OperatingSystem
Get-CimInstance -ClassName Win32_Service -Filter "State = 'Running'"
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine="notepad.exe"}
14.7 Real-World Examples
function Get-SystemInventory {
param(
[string[]]$ComputerName = $env:COMPUTERNAME
)
$results = @()
foreach ($computer in $ComputerName) {
try {
# Create CIM session
$session = New-CimSession -ComputerName $computer -ErrorAction Stop
# Get system information
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -CimSession $session
$os = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session
$cpu = Get-CimInstance -ClassName Win32_Processor -CimSession $session | Select-Object -First 1
$memory = Get-CimInstance -ClassName Win32_PhysicalMemory -CimSession $session
$disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -CimSession $session
# Calculate total RAM
$totalRAM = ($memory | Measure-Object -Property Capacity -Sum).Sum / 1GB
# Create inventory object
$inventory = [PSCustomObject]@{
ComputerName = $computer
Manufacturer = $cs.Manufacturer
Model = $cs.Model
SerialNumber = (Get-CimInstance -ClassName Win32_BIOS -CimSession $session).SerialNumber
OS = $os.Caption
OSVersion = $os.Version
OSArchitecture = $os.OSArchitecture
LastBootTime = $os.LastBootUpTime
Processor = $cpu.Name
Cores = $cpu.NumberOfCores
LogicalProcessors = $cpu.NumberOfLogicalProcessors
TotalRAM_GB = [math]::Round($totalRAM, 2)
Disks = ($disk | ForEach-Object {
"$($_.DeviceID) $([math]::Round($_.Size/1GB,2))GB"
}) -join ", "
Status = "Success"
}
$results += $inventory
# Clean up
Remove-CimSession -CimSession $session
}
catch {
$results += [PSCustomObject]@{
ComputerName = $computer
Status = "Failed: $($_.Exception.Message)"
}
}
}
return $results
}
# Usage
$inventory = Get-SystemInventory -ComputerName "localhost"
$inventory | Format-List
# Export to CSV
# $inventory | Export-Csv -Path "C:\Inventory.csv" -NoTypeInformation
function Get-DiskSpaceReport {
param(
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$WarningThreshold = 20,
[int]$CriticalThreshold = 10
)
$report = @()
foreach ($computer in $ComputerName) {
try {
$session = New-CimSession -ComputerName $computer -ErrorAction Stop
# Get logical disks (Type 3 = Local Disk)
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -CimSession $session
foreach ($disk in $disks) {
$freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
# Determine status
$status = switch ($freePercent) {
{$_ -le $CriticalThreshold} { "CRITICAL" }
{$_ -le $WarningThreshold} { "WARNING" }
default { "OK" }
}
$diskInfo = [PSCustomObject]@{
ComputerName = $computer
Drive = $disk.DeviceID
VolumeName = $disk.VolumeName
SizeGB = [math]::Round($disk.Size / 1GB, 2)
FreeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
UsedGB = [math]::Round(($disk.Size - $disk.FreeSpace) / 1GB, 2)
FreePercent = $freePercent
Status = $status
}
$report += $diskInfo
}
Remove-CimSession -CimSession $session
}
catch {
Write-Warning "Failed to query $computer: $_"
}
}
return $report
}
# Usage
$diskReport = Get-DiskSpaceReport -ComputerName "localhost" -WarningThreshold 20 -CriticalThreshold 10
# Display with color coding
$diskReport | ForEach-Object {
$color = switch ($_.Status) {
"CRITICAL" { "Red" }
"WARNING" { "Yellow" }
default { "Green" }
}
Write-Host "$($_.ComputerName) - $($_.Drive) - $($_.FreePercent)% free - $($_.Status)" -ForegroundColor $color
}
# Export critical/warning disks
$diskReport | Where-Object {$_.Status -ne "OK"} | Export-Csv -Path "C:\DiskAlerts.csv" -NoTypeInformation
function Get-NetworkConfiguration {
param(
[string]$ComputerName = $env:COMPUTERNAME
)
try {
$session = New-CimSession -ComputerName $ComputerName -ErrorAction Stop
# Get enabled network adapters
$adapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = True" -CimSession $session
$results = @()
foreach ($adapter in $adapters) {
$adapterInfo = [PSCustomObject]@{
ComputerName = $ComputerName
Description = $adapter.Description
MACAddress = $adapter.MACAddress
IPAddress = $adapter.IPAddress -join ", "
SubnetMask = $adapter.IPSubnet -join ", "
DefaultGateway = $adapter.DefaultIPGateway -join ", "
DNSServers = $adapter.DNSServerSearchOrder -join ", "
DHCPEnabled = $adapter.DHCPEnabled
DHCPServer = $adapter.DHCPServer
}
$results += $adapterInfo
}
Remove-CimSession -CimSession $session
return $results
}
catch {
Write-Error "Failed to get network configuration: $_"
}
}
# Usage
$netConfig = Get-NetworkConfiguration
$netConfig | Format-List
function Get-ServiceDependencies {
param(
[Parameter(Mandatory=$true)]
[string]$ServiceName,
[string]$ComputerName = $env:COMPUTERNAME
)
try {
$session = New-CimSession -ComputerName $ComputerName -ErrorAction Stop
# Get the service
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name = '$ServiceName'" -CimSession $session
if (-not $service) {
Write-Warning "Service '$ServiceName' not found on $ComputerName"
return
}
Write-Host "`n=== Service Information ===" -ForegroundColor Cyan
Write-Host "Name: $($service.Name)"
Write-Host "Display Name: $($service.DisplayName)"
Write-Host "State: $($service.State)"
Write-Host "Start Mode: $($service.StartMode)"
# Get services this service depends on
Write-Host "`n=== Services This Depends On ===" -ForegroundColor Cyan
$query = "SELECT * FROM Win32_DependentService WHERE Dependent = 'Win32_Service.Name=''$ServiceName'''"
$dependencies = Get-CimInstance -Query $query -CimSession $session
if ($dependencies) {
foreach ($dep in $dependencies) {
$depService = Get-CimInstance -ClassName Win32_Service -Filter "Name = '$($dep.Antecedent.Name)'" -CimSession $session
Write-Host " - $($depService.DisplayName) ($($depService.Name)) - State: $($depService.State)"
}
} else {
Write-Host " No dependencies"
}
# Get services that depend on this service
Write-Host "`n=== Services That Depend On This ===" -ForegroundColor Cyan
$query = "SELECT * FROM Win32_DependentService WHERE Antecedent = 'Win32_Service.Name=''$ServiceName'''"
$dependents = Get-CimInstance -Query $query -CimSession $session
if ($dependents) {
foreach ($dep in $dependents) {
$depService = Get-CimInstance -ClassName Win32_Service -Filter "Name = '$($dep.Dependent.Name)'" -CimSession $session
Write-Host " - $($depService.DisplayName) ($($depService.Name)) - State: $($depService.State)"
}
} else {
Write-Host " No dependent services"
}
Remove-CimSession -CimSession $session
}
catch {
Write-Error "Failed to get service dependencies: $_"
}
}
# Usage
Get-ServiceDependencies -ServiceName "Spooler"
Get-ServiceDependencies -ServiceName "W32Time"
14.8 Best Practices for WMI/CIM
β WMI/CIM Best Practices:
- Use CIM cmdlets: Always prefer CIM over WMI for new scripts
- Use CIM Sessions: Create sessions for multiple queries to same computer
- Server-side filtering: Use -Filter parameter instead of Where-Object when possible
- Select properties: Only retrieve properties you need for better performance
- Close sessions: Always clean up CIM sessions with Remove-CimSession
- Error handling: Use try/catch blocks for CIM operations
- Avoid Win32_Product: This class is very slow and can trigger repairs
- Use -ErrorAction: Control error behavior appropriately
- Test queries: Test WMI/CIM queries on test systems first
- Document classes: Comment which CIM classes you're using and why
# SLOW - Client-side filtering
Measure-Command {
Get-CimInstance -ClassName Win32_Service | Where-Object {$_.State -eq 'Running'}
}
# FAST - Server-side filtering
Measure-Command {
Get-CimInstance -ClassName Win32_Service -Filter "State = 'Running'"
}
# SLOW - Getting all properties
Measure-Command {
Get-CimInstance -ClassName Win32_Process
}
# FAST - Getting only needed properties
Measure-Command {
Get-CimInstance -ClassName Win32_Process | Select-Object Name, ProcessId, WorkingSetSize
}
14.9 Useful CIM Classes Reference
Quick Reference - Common CIM Queries:
| Information Needed | CIM Class | Example Query |
|---|---|---|
| Computer name | Win32_ComputerSystem | Get-CimInstance Win32_ComputerSystem | Select Name |
| OS version | Win32_OperatingSystem | Get-CimInstance Win32_OperatingSystem | Select Caption, Version |
| Last boot time | Win32_OperatingSystem | Get-CimInstance Win32_OperatingSystem | Select LastBootUpTime |
| CPU info | Win32_Processor | Get-CimInstance Win32_Processor | Select Name, NumberOfCores |
| Total RAM | Win32_ComputerSystem | Get-CimInstance Win32_ComputerSystem | Select TotalPhysicalMemory |
| Disk space | Win32_LogicalDisk | Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
| Running services | Win32_Service | Get-CimInstance Win32_Service -Filter "State='Running'" |
| Running processes | Win32_Process | Get-CimInstance Win32_Process |
| Network adapters | Win32_NetworkAdapterConfiguration | Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" |
| Event logs | Win32_NTLogEvent | Get-CimInstance Win32_NTLogEvent -Filter "LogFile='System'" |
14.10 Chapter Summary
Key Takeaways:
- β CIM cmdlets are the modern replacement for WMI cmdlets
- β Use Get-CimInstance to query WMI/CIM classes
- β Use -Filter parameter for server-side filtering (faster)
- β Create CIM sessions with New-CimSession for multiple queries
- β Use Invoke-CimMethod to execute WMI methods
- β Common classes: Win32_OperatingSystem, Win32_ComputerSystem, Win32_Service, Win32_Process
- β Always close CIM sessions with Remove-CimSession
- β CIM works better with PowerShell remoting than WMI
- β Avoid Win32_Product class (very slow)
- β Use try/catch blocks for error handling
Chapter 15: Remote Management with PowerShell
15.1 Introduction to PowerShell Remoting
PowerShell Remoting enables you to run commands on remote computers. It's built on the WS-Management protocol and provides secure, efficient remote administration capabilities.
PowerShell Remoting Benefits:
- Secure: Uses Kerberos authentication by default
- Efficient: Reuses connections with sessions
- Scalable: Can manage multiple computers simultaneously
- Flexible: Run interactive or background commands
- Built-in: Native Windows feature (Windows Server 2012+)
Remoting Methods Comparison:
| Method | Use Case | Connection Type |
|---|---|---|
| Invoke-Command | Run commands on remote computers | One-time or persistent session |
| Enter-PSSession | Interactive remote session (1-to-1) | Interactive |
| New-PSSession | Create persistent connection | Persistent session |
| CIM Sessions | WMI/CIM queries | CIM-specific |
15.2 Enabling PowerShell Remoting
# Enable PowerShell Remoting (run as Administrator)
Enable-PSRemoting -Force
# What Enable-PSRemoting does:
# 1. Starts WinRM service
# 2. Sets WinRM service to automatic startup
# 3. Creates listener on HTTP (port 5985)
# 4. Enables firewall rules for WS-Management
# 5. Registers PowerShell session configurations
# Check if WinRM service is running
Get-Service WinRM
# Test WinRM configuration
Test-WSMan
# Test connection to remote computer
Test-WSMan -ComputerName "Server01"
# View WinRM configuration
winrm get winrm/config
# View current trusted hosts
Get-Item WSMan:\localhost\Client\TrustedHosts
# Add specific computer to trusted hosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "Server01"
# Add multiple computers
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "Server01,Server02,Server03"
# Add all computers (use with caution!)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*"
# Append to existing list
$current = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "$current,Server04"
15.3 Interactive Remote Sessions (One-to-One)
# Start interactive session to remote computer
Enter-PSSession -ComputerName "Server01"
# With credentials (for workgroup or different domain)
$cred = Get-Credential
Enter-PSSession -ComputerName "Server01" -Credential $cred
# Using existing session
$session = New-PSSession -ComputerName "Server01"
Enter-PSSession -Session $session
# Exit interactive session
Exit-PSSession
# Or use 'exit' command
Interactive Session Indicators:
- Prompt changes to:
[Server01]: PS C:\> - All commands run on remote computer
- Use
Exit-PSSessionorexitto return to local session - Session remains active until explicitly closed
15.4 Running Commands on Multiple Computers (One-to-Many)
# Run command on single remote computer
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Get-Service -Name "Spooler"
}
# Run command on multiple computers
Invoke-Command -ComputerName "Server01", "Server02", "Server03" -ScriptBlock {
Get-Process | Where-Object {$_.CPU -gt 100}
}
# Run command with credentials
$cred = Get-Credential
Invoke-Command -ComputerName "Server01" -Credential $cred -ScriptBlock {
Get-EventLog -LogName System -Newest 10
}
# Run local script on remote computers
Invoke-Command -ComputerName "Server01", "Server02" -FilePath "C:\Scripts\Maintenance.ps1"
# Using -ArgumentList parameter
$serviceName = "Spooler"
Invoke-Command -ComputerName "Server01" -ScriptBlock {
param($Service)
Get-Service -Name $Service
} -ArgumentList $serviceName
# Multiple arguments
$service = "Spooler"
$status = "Running"
Invoke-Command -ComputerName "Server01" -ScriptBlock {
param($ServiceName, $ServiceStatus)
Get-Service -Name $ServiceName | Where-Object {$_.Status -eq $ServiceStatus}
} -ArgumentList $service, $status
# Using $using: scope modifier (PowerShell 3.0+)
$serviceName = "Spooler"
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Get-Service -Name $using:serviceName
}
# Results include PSComputerName property
$results = Invoke-Command -ComputerName "Server01", "Server02", "Server03" -ScriptBlock {
Get-Service -Name "Spooler"
}
# Display with computer name
$results | Select-Object PSComputerName, Name, Status | Format-Table -AutoSize
# Filter results by computer
$results | Where-Object {$_.PSComputerName -eq "Server01"}
# Export results
$results | Export-Csv -Path "C:\Reports\ServiceStatus.csv" -NoTypeInformation
15.5 Working with Persistent Sessions
# Create single session
$session = New-PSSession -ComputerName "Server01"
# Create multiple sessions
$sessions = New-PSSession -ComputerName "Server01", "Server02", "Server03"
# Create session with credentials
$cred = Get-Credential
$session = New-PSSession -ComputerName "Server01" -Credential $cred
# Create session with custom name
$session = New-PSSession -ComputerName "Server01" -Name "MyServerSession"
# View active sessions
Get-PSSession
# View sessions on specific computer
Get-PSSession -ComputerName "Server01"
# Create session
$session = New-PSSession -ComputerName "Server01"
# Run multiple commands using same session
Invoke-Command -Session $session -ScriptBlock {
$data = Get-Process
$data | Where-Object {$_.CPU -gt 100}
}
Invoke-Command -Session $session -ScriptBlock {
# Variables from previous command are still available
$data | Measure-Object -Property WorkingSet -Sum
}
# Enter interactive mode with session
Enter-PSSession -Session $session
# Close session when done
Remove-PSSession -Session $session
# Or close all sessions
Get-PSSession | Remove-PSSession
Benefits of Persistent Sessions:
- β Performance: Reuse connection, avoid authentication overhead
- β State preservation: Variables and data persist between commands
- β Efficiency: Better for multiple operations on same computer
- β Resource management: Explicit control over connection lifecycle
15.6 Advanced Remoting Techniques
# Run remote command as background job
$job = Invoke-Command -ComputerName "Server01", "Server02" -ScriptBlock {
Get-EventLog -LogName System -Newest 1000
} -AsJob
# Check job status
Get-Job
# Wait for job to complete
Wait-Job -Job $job
# Get job results
$results = Receive-Job -Job $job
# Remove job
Remove-Job -Job $job
# Run with job name
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Get-Process
} -AsJob -JobName "ProcessInventory"
# Default throttle limit is 32 concurrent connections
Invoke-Command -ComputerName (Get-Content "C:\Servers.txt") -ScriptBlock {
Get-Service
}
# Set custom throttle limit
Invoke-Command -ComputerName (Get-Content "C:\Servers.txt") -ScriptBlock {
Get-Service
} -ThrottleLimit 10
# Process large list efficiently
$servers = 1..100 | ForEach-Object { "Server$_" }
Invoke-Command -ComputerName $servers -ScriptBlock {
Get-WmiObject Win32_OperatingSystem
} -ThrottleLimit 20
# Create session
$session = New-PSSession -ComputerName "Server01"
# Start long-running command
Invoke-Command -Session $session -ScriptBlock {
Start-Sleep -Seconds 300
Get-Process
} -AsJob
# Disconnect session (keeps running on remote computer)
Disconnect-PSSession -Session $session
# Later, reconnect to session
$session = Connect-PSSession -ComputerName "Server01"
# Or get disconnected sessions
$disconnected = Get-PSSession -ComputerName "Server01" -State Disconnected
Connect-PSSession -Session $disconnected[0]
15.7 Real-World Remote Management Examples
function Get-ServerHealthCheck {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerName,
[PSCredential]$Credential
)
$results = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
# Get system information
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$cs = Get-CimInstance -ClassName Win32_ComputerSystem
$disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3"
# Calculate uptime
$uptime = (Get-Date) - $os.LastBootUpTime
# Get CPU usage
$cpu = Get-CimInstance -ClassName Win32_Processor
$cpuLoad = ($cpu | Measure-Object -Property LoadPercentage -Average).Average
# Memory usage
$memoryUsedPercent = [math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 2)
# Disk space
$diskInfo = $disk | ForEach-Object {
$freePercent = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2)
[PSCustomObject]@{
Drive = $_.DeviceID
FreePercent = $freePercent
Status = if ($freePercent -lt 10) { "CRITICAL" } elseif ($freePercent -lt 20) { "WARNING" } else { "OK" }
}
}
# Critical services
$criticalServices = @("WinRM", "W32Time", "EventLog")
$serviceStatus = Get-Service -Name $criticalServices -ErrorAction SilentlyContinue
$stoppedServices = $serviceStatus | Where-Object {$_.Status -ne "Running"}
# Return health report
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
OS = $os.Caption
UptimeDays = [math]::Round($uptime.TotalDays, 2)
CPULoad = $cpuLoad
MemoryUsedPercent = $memoryUsedPercent
DiskStatus = ($diskInfo | Where-Object {$_.Status -ne "OK"}).Count -eq 0
DiskDetails = $diskInfo
StoppedCriticalServices = $stoppedServices.Name -join ", "
OverallStatus = if ($stoppedServices -or ($diskInfo | Where-Object {$_.Status -eq "CRITICAL"})) {
"CRITICAL"
} elseif (($diskInfo | Where-Object {$_.Status -eq "WARNING"}) -or $memoryUsedPercent -gt 90) {
"WARNING"
} else {
"HEALTHY"
}
}
} -ErrorAction SilentlyContinue
return $results
}
# Usage
$servers = "Server01", "Server02", "Server03"
$healthCheck = Get-ServerHealthCheck -ComputerName $servers
# Display results
$healthCheck | Format-Table ComputerName, OS, UptimeDays, CPULoad, MemoryUsedPercent, OverallStatus -AutoSize
# Export to HTML report
$healthCheck | ConvertTo-Html -Title "Server Health Report" | Out-File "C:\Reports\ServerHealth.html"
function Install-RemoteSoftware {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerName,
[Parameter(Mandatory=$true)]
[string]$InstallerPath,
[string]$Arguments = "/quiet /norestart",
[PSCredential]$Credential
)
# Verify installer exists
if (-not (Test-Path $InstallerPath)) {
Write-Error "Installer not found: $InstallerPath"
return
}
$installerName = Split-Path $InstallerPath -Leaf
$results = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
param($InstallerContent, $InstallerName, $Arguments)
try {
# Create temp directory
$tempPath = "C:\Temp\RemoteInstall"
if (-not (Test-Path $tempPath)) {
New-Item -Path $tempPath -ItemType Directory -Force | Out-Null
}
# Save installer
$localInstaller = Join-Path $tempPath $InstallerName
[System.IO.File]::WriteAllBytes($localInstaller, $InstallerContent)
# Run installer
Write-Host "Installing $InstallerName..."
$process = Start-Process -FilePath $localInstaller -ArgumentList $Arguments -Wait -PassThru -NoNewWindow
# Clean up
Remove-Item -Path $localInstaller -Force -ErrorAction SilentlyContinue
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Status = if ($process.ExitCode -eq 0) { "Success" } else { "Failed" }
ExitCode = $process.ExitCode
Message = if ($process.ExitCode -eq 0) { "Installation completed" } else { "Installation failed with exit code $($process.ExitCode)" }
}
}
catch {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Status = "Error"
ExitCode = -1
Message = $_.Exception.Message
}
}
} -ArgumentList ([System.IO.File]::ReadAllBytes($InstallerPath)), $installerName, $Arguments
return $results
}
# Usage
$servers = "Server01", "Server02"
$installer = "C:\Software\MyApp_Setup.msi"
$results = Install-RemoteSoftware -ComputerName $servers -InstallerPath $installer -Arguments "/quiet /norestart"
# Display results
$results | Format-Table ComputerName, Status, ExitCode, Message -AutoSize
function Get-RemoteEventLogs {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerName,
[string]$LogName = "System",
[int]$Newest = 100,
[string[]]$EntryType = @("Error", "Warning"),
[PSCredential]$Credential
)
$logs = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
param($LogName, $Newest, $EntryType)
try {
$events = Get-EventLog -LogName $LogName -Newest $Newest -EntryType $EntryType -ErrorAction Stop
$events | ForEach-Object {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
TimeGenerated = $_.TimeGenerated
EntryType = $_.EntryType
Source = $_.Source
EventID = $_.EventID
Message = $_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))
}
}
}
catch {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
TimeGenerated = Get-Date
EntryType = "Error"
Source = "PowerShell"
EventID = 0
Message = "Failed to retrieve logs: $($_.Exception.Message)"
}
}
} -ArgumentList $LogName, $Newest, $EntryType
return $logs
}
# Usage
$servers = "Server01", "Server02", "Server03"
$errorLogs = Get-RemoteEventLogs -ComputerName $servers -LogName "System" -Newest 50 -EntryType "Error"
# Display summary
$errorLogs | Group-Object ComputerName | Select-Object Name, Count
# Export to CSV
$errorLogs | Export-Csv -Path "C:\Reports\RemoteErrors.csv" -NoTypeInformation
# Show recent critical events
$errorLogs | Where-Object {$_.TimeGenerated -gt (Get-Date).AddHours(-24)} |
Format-Table ComputerName, TimeGenerated, Source, EventID, Message -AutoSize
function Set-RemoteConfiguration {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerName,
[Parameter(Mandatory=$true)]
[hashtable]$Configuration,
[PSCredential]$Credential
)
$results = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
param($Config)
$changes = @()
try {
# Example: Set registry values
if ($Config.ContainsKey("Registry")) {
foreach ($regItem in $Config.Registry.GetEnumerator()) {
$path = $regItem.Value.Path
$name = $regItem.Value.Name
$value = $regItem.Value.Value
$type = $regItem.Value.Type
if (-not (Test-Path $path)) {
New-Item -Path $path -Force | Out-Null
}
Set-ItemProperty -Path $path -Name $name -Value $value -Type $type
$changes += "Registry: Set $path\$name = $value"
}
}
# Example: Configure services
if ($Config.ContainsKey("Services")) {
foreach ($svcItem in $Config.Services.GetEnumerator()) {
$svcName = $svcItem.Key
$svcConfig = $svcItem.Value
$service = Get-Service -Name $svcName -ErrorAction SilentlyContinue
if ($service) {
if ($svcConfig.StartupType) {
Set-Service -Name $svcName -StartupType $svcConfig.StartupType
$changes += "Service: Set $svcName startup to $($svcConfig.StartupType)"
}
if ($svcConfig.Status -eq "Running" -and $service.Status -ne "Running") {
Start-Service -Name $svcName
$changes += "Service: Started $svcName"
}
elseif ($svcConfig.Status -eq "Stopped" -and $service.Status -ne "Stopped") {
Stop-Service -Name $svcName -Force
$changes += "Service: Stopped $svcName"
}
}
}
}
# Example: Set environment variables
if ($Config.ContainsKey("Environment")) {
foreach ($envItem in $Config.Environment.GetEnumerator()) {
[Environment]::SetEnvironmentVariable($envItem.Key, $envItem.Value, "Machine")
$changes += "Environment: Set $($envItem.Key) = $($envItem.Value)"
}
}
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Status = "Success"
ChangesApplied = $changes.Count
Changes = $changes -join "; "
}
}
catch {
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
Status = "Failed"
ChangesApplied = 0
Changes = "Error: $($_.Exception.Message)"
}
}
} -ArgumentList $Configuration
return $results
}
# Usage example
$config = @{
Services = @{
"Spooler" = @{
StartupType = "Automatic"
Status = "Running"
}
"W32Time" = @{
StartupType = "Automatic"
Status = "Running"
}
}
Environment = @{
"COMPANY_NAME" = "MyCompany"
"APP_ENV" = "Production"
}
Registry = @{
"AppSetting1" = @{
Path = "HKLM:\SOFTWARE\MyApp"
Name = "Setting1"
Value = "Value1"
Type = "String"
}
}
}
$servers = "Server01", "Server02"
$results = Set-RemoteConfiguration -ComputerName $servers -Configuration $config
# Display results
$results | Format-Table ComputerName, Status, ChangesApplied -AutoSize
$results | Select-Object ComputerName, Changes | Format-List
15.8 Troubleshooting PowerShell Remoting
Common Remoting Issues and Solutions:
| Issue | Possible Cause | Solution |
|---|---|---|
| Access Denied | Insufficient permissions | Use administrator credentials or add user to Remote Management Users group |
| WinRM cannot complete | WinRM not enabled | Run Enable-PSRemoting -Force on target |
| Connection timeout | Firewall blocking ports | Open ports 5985 (HTTP) or 5986 (HTTPS) |
| Cannot verify hostname | Not in TrustedHosts (workgroup) | Add computer to TrustedHosts or use HTTPS |
| Authentication failed | Wrong credentials or Kerberos issue | Verify credentials, check domain trust |
# Test basic connectivity
Test-NetConnection -ComputerName "Server01" -Port 5985
# Test WinRM
Test-WSMan -ComputerName "Server01"
# Check WinRM service on remote computer
Get-Service WinRM -ComputerName "Server01"
# View WinRM listeners
Get-WSManInstance -ResourceURI winrm/config/listener -Enumerate
# Check firewall rules
Get-NetFirewallRule -DisplayName "*Windows Remote Management*"
# Enable detailed logging
Set-Item WSMan:\localhost\Service\Auth\Basic -Value $true
Set-Item WSMan:\localhost\Client\Auth\Basic -Value $true
# View WinRM event logs
Get-EventLog -LogName "Microsoft-Windows-WinRM/Operational" -Newest 50
15.9 Best Practices for Remote Management
β Remote Management Best Practices:
- Use CIM sessions: Prefer CIM cmdlets over WMI for better performance
- Persistent sessions: Use New-PSSession for multiple operations
- Close sessions: Always clean up with Remove-PSSession
- Error handling: Use try/catch blocks for remote operations
- Throttle limits: Set appropriate -ThrottleLimit for large deployments
- Credentials: Store credentials securely, avoid plaintext
- TrustedHosts: Use sparingly, prefer domain authentication
- HTTPS: Use HTTPS (port 5986) for sensitive operations
- Logging: Log remote operations for audit trail
- Test first: Always test on non-production systems
# Store encrypted credential to file (user-specific encryption)
$cred = Get-Credential
$cred | Export-Clixml -Path "C:\Secure\Credential.xml"
# Load credential from file
$cred = Import-Clixml -Path "C:\Secure\Credential.xml"
# Use credential in remote command
Invoke-Command -ComputerName "Server01" -Credential $cred -ScriptBlock {
Get-Service
}
# Create credential from secure string
$username = "Domain\User"
$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($username, $password)
15.10 Chapter Summary
Key Takeaways:
- β Enable-PSRemoting configures WinRM for remote management
- β Enter-PSSession for interactive 1-to-1 remote sessions
- β Invoke-Command for running commands on multiple computers
- β New-PSSession creates persistent connections for efficiency
- β Use -AsJob for background remote operations
- β TrustedHosts needed for workgroup environments
- β ThrottleLimit controls concurrent connections
- β Always close sessions with Remove-PSSession
- β Use $using: scope for passing variables to remote scripts
- β Test-WSMan verifies WinRM connectivity
Chapter 16: Modules and Script Management
16.1 Introduction to PowerShell Modules
PowerShell modules are packages that contain cmdlets, functions, variables, and other resources. They enable code reuse, simplify distribution, and provide a structured way to organize PowerShell functionality.
Module Types:
| Type | Extension | Description | Use Case |
|---|---|---|---|
| Script Module | .psm1 | PowerShell script file | Custom functions and scripts |
| Binary Module | .dll | Compiled .NET assembly | High-performance cmdlets |
| Manifest Module | .psd1 | Module metadata file | Version control, dependencies |
| Dynamic Module | N/A | Created in memory | Temporary, session-specific |
16.2 Working with Modules
# List currently loaded modules
Get-Module
# List all available modules (installed)
Get-Module -ListAvailable
# Find specific module
Get-Module -Name "Microsoft.PowerShell.*" -ListAvailable
# Get module details
Get-Module -Name "Microsoft.PowerShell.Management" | Format-List
# Import a module
Import-Module -Name "ActiveDirectory"
# Import module from specific path
Import-Module -Name "C:\MyModules\MyModule.psm1"
# Import with specific version
Import-Module -Name "Az" -RequiredVersion "9.0.0"
# Import and force reload (refresh)
Import-Module -Name "MyModule" -Force
# Import specific functions only
Import-Module -Name "MyModule" -Function "Get-CustomData", "Set-CustomData"
# Remove a loaded module
Remove-Module -Name "MyModule"
# Remove all loaded modules
Get-Module | Remove-Module
# Remove with confirmation
Remove-Module -Name "MyModule" -Confirm
16.3 Module Paths and Discovery
# View current module paths
$env:PSModulePath -split ';'
# Common module paths:
# C:\Users\<Username>\Documents\PowerShell\Modules (User)
# C:\Program Files\PowerShell\Modules (All Users)
# C:\Windows\System32\WindowsPowerShell\v1.0\Modules (System)
# Add custom module path (temporary - current session)
$env:PSModulePath += ";C:\MyCustomModules"
# Add custom module path (permanent - user)
$currentPath = [Environment]::GetEnvironmentVariable("PSModulePath", "User")
$newPath = "$currentPath;C:\MyCustomModules"
[Environment]::SetEnvironmentVariable("PSModulePath", $newPath, "User")
# Verify module can be found
Get-Module -Name "MyModule" -ListAvailable
16.4 Creating Your First Module
# Create module directory
$modulePath = "$HOME\Documents\PowerShell\Modules\MyFirstModule"
New-Item -Path $modulePath -ItemType Directory -Force
# Create module file
$moduleFile = "$modulePath\MyFirstModule.psm1"
# Module content
$moduleContent = @'
# MyFirstModule.psm1
function Get-Greeting {
<#
.SYNOPSIS
Returns a personalized greeting.
.DESCRIPTION
This function returns a greeting message with the specified name.
.PARAMETER Name
The name to include in the greeting.
.EXAMPLE
Get-Greeting -Name "John"
Returns: "Hello, John! Welcome to PowerShell modules."
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Name
)
return "Hello, $Name! Welcome to PowerShell modules."
}
function Get-SystemInfo {
<#
.SYNOPSIS
Gets basic system information.
.DESCRIPTION
Returns computer name, OS, and PowerShell version.
.EXAMPLE
Get-SystemInfo
#>
[CmdletBinding()]
param()
[PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
UserName = $env:USERNAME
OS = (Get-CimInstance Win32_OperatingSystem).Caption
PSVersion = $PSVersionTable.PSVersion.ToString()
Date = Get-Date
}
}
# Export functions (make them available when module is imported)
Export-ModuleMember -Function Get-Greeting, Get-SystemInfo
'@
# Save module file
Set-Content -Path $moduleFile -Value $moduleContent -Encoding UTF8
Write-Host "Module created: $moduleFile" -ForegroundColor Green
# Import the module
Import-Module -Name "MyFirstModule" -Force
# Test the functions
Get-Greeting -Name "PowerShell User"
Get-SystemInfo
# View module information
Get-Module -Name "MyFirstModule"
# View exported commands
Get-Command -Module "MyFirstModule"
16.5 Creating a Module Manifest
# Create module manifest
$manifestPath = "$HOME\Documents\PowerShell\Modules\MyFirstModule\MyFirstModule.psd1"
$manifestParams = @{
Path = $manifestPath
RootModule = "MyFirstModule.psm1"
ModuleVersion = "1.0.0"
Author = "Your Name"
CompanyName = "Your Company"
Description = "My first PowerShell module with custom functions"
PowerShellVersion = "5.1"
FunctionsToExport = @("Get-Greeting", "Get-SystemInfo")
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
Tags = @("Demo", "Learning", "Tutorial")
ProjectUri = "https://github.com/yourusername/MyFirstModule"
LicenseUri = "https://github.com/yourusername/MyFirstModule/LICENSE"
ReleaseNotes = "Initial release with basic functions"
}
New-ModuleManifest @manifestParams
Write-Host "Manifest created: $manifestPath" -ForegroundColor Green
# Test the manifest file
Test-ModuleManifest -Path "$HOME\Documents\PowerShell\Modules\MyFirstModule\MyFirstModule.psd1"
# Import module using manifest
Import-Module -Name "MyFirstModule" -Force
# View manifest details
Get-Module -Name "MyFirstModule" | Format-List
16.6 Advanced Module Features
# Advanced module structure
$advancedModule = @'
# MyAdvancedModule.psm1
# Private function (not exported)
function Get-InternalData {
param([string]$Value)
return "Internal: $Value"
}
# Public function (exported)
function Get-PublicData {
<#
.SYNOPSIS
Gets public data using internal helper.
.PARAMETER Value
The value to process.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Value
)
# Call private function
$internal = Get-InternalData -Value $Value
return "Public: $Value | $internal"
}
# Module initialization code
Write-Verbose "MyAdvancedModule loaded"
# Export only public functions
Export-ModuleMember -Function Get-PublicData
'@
# Save advanced module
$advModulePath = "$HOME\Documents\PowerShell\Modules\MyAdvancedModule"
New-Item -Path $advModulePath -ItemType Directory -Force
Set-Content -Path "$advModulePath\MyAdvancedModule.psm1" -Value $advancedModule -Encoding UTF8
# Module with PowerShell classes
$classModule = @'
# MyClassModule.psm1
# Define a class
class Computer {
[string]$Name
[string]$OS
[datetime]$LastBoot
# Constructor
Computer([string]$computerName) {
$this.Name = $computerName
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computerName
$this.OS = $os.Caption
$this.LastBoot = $os.LastBootUpTime
}
# Method
[string]GetUptime() {
$uptime = (Get-Date) - $this.LastBoot
return "$($uptime.Days) days, $($uptime.Hours) hours"
}
}
# Function that uses the class
function Get-ComputerObject {
<#
.SYNOPSIS
Creates a Computer object.
.PARAMETER ComputerName
The name of the computer.
#>
[CmdletBinding()]
param(
[string]$ComputerName = $env:COMPUTERNAME
)
return [Computer]::new($ComputerName)
}
# Export class and function
Export-ModuleMember -Function Get-ComputerObject
'@
# Save class module
$classModulePath = "$HOME\Documents\PowerShell\Modules\MyClassModule"
New-Item -Path $classModulePath -ItemType Directory -Force
Set-Content -Path "$classModulePath\MyClassModule.psm1" -Value $classModule -Encoding UTF8
# Usage example:
# Import-Module MyClassModule
# $pc = Get-ComputerObject
# $pc.GetUptime()
16.7 Script Management Best Practices
<#
.SYNOPSIS
Brief description of the script.
.DESCRIPTION
Detailed description of what the script does.
.PARAMETER ComputerName
The name of the computer to query.
.PARAMETER LogPath
Path to the log file.
.EXAMPLE
.\MyScript.ps1 -ComputerName "Server01" -LogPath "C:\Logs\script.log"
.NOTES
Author: Your Name
Date: 2024-01-01
Version: 1.0
.LINK
https://github.com/yourusername/yourrepo
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, HelpMessage="Enter computer name")]
[ValidateNotNullOrEmpty()]
[string]$ComputerName,
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path (Split-Path $_)})]
[string]$LogPath = "$PSScriptRoot\script.log"
)
# Script variables
$ErrorActionPreference = "Stop"
$scriptVersion = "1.0"
$scriptName = $MyInvocation.MyCommand.Name
# Logging function
function Write-Log {
param(
[string]$Message,
[ValidateSet("INFO", "WARNING", "ERROR")]
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logMessage = "[$timestamp] [$Level] $Message"
# Write to console
switch ($Level) {
"INFO" { Write-Host $logMessage -ForegroundColor Green }
"WARNING" { Write-Host $logMessage -ForegroundColor Yellow }
"ERROR" { Write-Host $logMessage -ForegroundColor Red }
}
# Write to file
Add-Content -Path $LogPath -Value $logMessage
}
# Main script logic
try {
Write-Log "Script started: $scriptName v$scriptVersion"
Write-Log "Target computer: $ComputerName"
# Your script logic here
$result = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName
Write-Log "Successfully retrieved information from $ComputerName"
# Return result
return $result
}
catch {
Write-Log "Error: $($_.Exception.Message)" -Level ERROR
throw
}
finally {
Write-Log "Script completed"
}
# View current execution policy
Get-ExecutionPolicy -List
# Set execution policy (requires admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Execution policy levels:
# - Restricted: No scripts allowed
# - AllSigned: Only signed scripts
# - RemoteSigned: Local scripts OK, remote must be signed
# - Unrestricted: All scripts allowed (prompts for remote)
# - Bypass: Nothing blocked, no warnings
# Create self-signed certificate for code signing (development only)
$cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=PowerShell Code Signing" -CertStoreLocation Cert:\CurrentUser\My
# Sign a script
$scriptPath = "C:\Scripts\MyScript.ps1"
Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $cert
# Verify signature
Get-AuthenticodeSignature -FilePath $scriptPath
16.8 PowerShell Gallery and Package Management
# Search for modules
Find-Module -Name "*Azure*"
# Find modules by tag
Find-Module -Tag "Backup"
# Find specific module with details
Find-Module -Name "Pester" | Format-List
# Find modules by command
Find-Module -Command "Get-AzVM"
# Search with wildcard
Find-Module -Name "PowerShell*" | Select-Object Name, Version, Description
# Install module for current user
Install-Module -Name "Pester" -Scope CurrentUser
# Install specific version
Install-Module -Name "Az" -RequiredVersion "9.0.0" -Scope CurrentUser
# Install with force (no prompts)
Install-Module -Name "PSReadLine" -Force -Scope CurrentUser
# Install and accept license
Install-Module -Name "PowerShellGet" -AcceptLicense -Scope CurrentUser
# Install from specific repository
Install-Module -Name "MyModule" -Repository "PSGallery"
# Update specific module
Update-Module -Name "Pester"
# Update all modules
Get-InstalledModule | Update-Module
# Check for available updates
Get-InstalledModule | ForEach-Object {
$installed = $_
$available = Find-Module -Name $_.Name -ErrorAction SilentlyContinue
if ($available -and ($available.Version -gt $installed.Version)) {
[PSCustomObject]@{
Name = $installed.Name
InstalledVersion = $installed.Version
AvailableVersion = $available.Version
NeedsUpdate = $true
}
}
}
# Uninstall specific module
Uninstall-Module -Name "OldModule"
# Uninstall specific version
Uninstall-Module -Name "Az" -RequiredVersion "8.0.0"
# Uninstall all versions
Get-InstalledModule -Name "Az" -AllVersions | Uninstall-Module
# Uninstall with force
Uninstall-Module -Name "MyModule" -Force
16.9 Real-World Module Examples
# SysAdminTools.psm1
# A module for common system administration tasks
function Get-DiskSpaceReport {
<#
.SYNOPSIS
Generates disk space report for specified computers.
#>
[CmdletBinding()]
param(
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$WarningThreshold = 20
)
$results = @()
foreach ($computer in $ComputerName) {
try {
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $computer
foreach ($disk in $disks) {
$freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
$results += [PSCustomObject]@{
ComputerName = $computer
Drive = $disk.DeviceID
SizeGB = [math]::Round($disk.Size / 1GB, 2)
FreeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
FreePercent = $freePercent
Status = if ($freePercent -lt $WarningThreshold) { "WARNING" } else { "OK" }
}
}
}
catch {
Write-Warning "Failed to query $computer: $_"
}
}
return $results
}
function Get-ServiceStatus {
<#
.SYNOPSIS
Gets status of specified services across multiple computers.
#>
[CmdletBinding()]
param(
[string[]]$ComputerName = $env:COMPUTERNAME,
[string[]]$ServiceName
)
$results = @()
foreach ($computer in $ComputerName) {
foreach ($service in $ServiceName) {
try {
$svc = Get-Service -Name $service -ComputerName $computer -ErrorAction Stop
$results += [PSCustomObject]@{
ComputerName = $computer
ServiceName = $svc.Name
DisplayName = $svc.DisplayName
Status = $svc.Status
StartType = $svc.StartType
}
}
catch {
$results += [PSCustomObject]@{
ComputerName = $computer
ServiceName = $service
Status = "ERROR: $($_.Exception.Message)"
}
}
}
}
return $results
}
# Export functions
Export-ModuleMember -Function Get-DiskSpaceReport, Get-ServiceStatus
# UserManagement.psm1
# Module for local user account management
function New-LocalUserAccount {
<#
.SYNOPSIS
Creates a new local user account.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$UserName,
[Parameter(Mandatory=$true)]
[SecureString]$Password,
[string]$FullName,
[string]$Description,
[switch]$PasswordNeverExpires
)
try {
$userParams = @{
Name = $UserName
Password = $Password
FullName = $FullName
Description = $Description
}
if ($PasswordNeverExpires) {
$userParams.PasswordNeverExpires = $true
}
New-LocalUser @userParams
Write-Host "User '$UserName' created successfully" -ForegroundColor Green
}
catch {
Write-Error "Failed to create user: $_"
}
}
function Get-LocalUserInfo {
<#
.SYNOPSIS
Gets detailed information about local users.
#>
[CmdletBinding()]
param(
[string]$UserName = "*"
)
Get-LocalUser -Name $UserName | Select-Object Name, Enabled, LastLogon,
PasswordRequired, PasswordExpires, Description |
Format-Table -AutoSize
}
# Export functions
Export-ModuleMember -Function New-LocalUserAccount, Get-LocalUserInfo
16.10 Module and Script Best Practices
Module Development Best Practices:
- Use Approved Verbs: Get-Verb to see approved verbs for function names
- Create Manifests: Always create .psd1 manifest files for versioning
- Comment-Based Help: Include comprehensive help for all functions
- Export Explicitly: Use Export-ModuleMember to control what's public
- Version Control: Use semantic versioning (Major.Minor.Patch)
- Error Handling: Implement proper try/catch blocks
- Parameter Validation: Use ValidateSet, ValidateRange, etc.
- Support -WhatIf: Add [CmdletBinding(SupportsShouldProcess)] for changes
- Test Thoroughly: Use Pester for unit testing
- Document Dependencies: List required modules in manifest
# Recommended module folder structure:
# MyModule/
# - MyModule.psd1 # Module manifest
# - MyModule.psm1 # Main module file
# - Public/ # Public functions (exported)
# - Get-Something.ps1
# - Set-Something.ps1
# - Private/ # Private functions (internal)
# - Helper-Function.ps1
# - Classes/ # PowerShell classes
# - MyClass.ps1
# - Tests/ # Pester tests
# - MyModule.Tests.ps1
# - README.md # Documentation
# - LICENSE # License file
# Example of loading structure in .psm1:
# Get public and private function files
$Public = @(Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue)
$Private = @(Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue)
# Dot source the files
foreach ($import in @($Public + $Private)) {
try {
. $import.FullName
}
catch {
Write-Error "Failed to import function $($import.FullName): $_"
}
}
# Export public functions
Export-ModuleMember -Function $Public.BaseName
Script Management Best Practices:
- Use Comment-Based Help: Document synopsis, description, parameters, examples
- Parameter Validation: Validate input before processing
- Error Handling: Use try/catch and $ErrorActionPreference
- Logging: Implement logging for troubleshooting
- Use $PSScriptRoot: Reference files relative to script location
- Version Your Scripts: Include version number in header
- Test Before Deploy: Test in non-production first
- Use Source Control: Git for version history
- Follow Naming Conventions: Verb-Noun format
- Code Signing: Sign scripts for production environments
16.11 Chapter Summary
Key Takeaways:
- Modules are the best way to package reusable PowerShell code
- Use Get-Module to list, Import-Module to load, Remove-Module to unload
- Create .psm1 files for script modules and .psd1 for manifests
- Use Export-ModuleMember to control what functions are public
- Store modules in $env:PSModulePath for auto-discovery
- Use PowerShell Gallery to find and install community modules
- Find-Module, Install-Module, Update-Module for package management
- Include comment-based help for all functions
- Follow approved verb-noun naming conventions
- Use proper folder structure with Public/Private separation
- Implement error handling and parameter validation
- Test modules with Pester before deployment
Chapter 17: Advanced PowerShell Topics
17.1 Introduction to Advanced PowerShell
Advanced PowerShell techniques enable you to write more efficient, maintainable, and professional scripts. This chapter explores advanced concepts that separate basic scripters from PowerShell experts.
Topics Covered in This Chapter:
- Advanced Functions: Parameter validation, pipeline input, dynamic parameters
- Regular Expressions: Pattern matching and text manipulation
- Background Jobs: Parallel execution and asynchronous operations
- PowerShell Classes: Object-oriented programming
- Advanced Scripting: Splatting, here-strings, script blocks
- Performance Optimization: Making scripts faster and more efficient
17.2 Advanced Functions and Parameters
function Get-AdvancedExample {
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
Position=0,
HelpMessage="Enter computer name")]
[ValidateNotNullOrEmpty()]
[Alias("CN","MachineName")]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[ValidateSet("Running","Stopped","All")]
[string]$Status = "All",
[Parameter(Mandatory=$false)]
[ValidateRange(1,100)]
[int]$MaxResults = 10,
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_})]
[string]$LogPath = "C:\Logs",
[switch]$Detailed
)
begin {
Write-Verbose "Starting function execution"
$results = @()
}
process {
foreach ($computer in $ComputerName) {
if ($PSCmdlet.ShouldProcess($computer, "Get information")) {
Write-Verbose "Processing $computer"
try {
# Your logic here
$result = [PSCustomObject]@{
ComputerName = $computer
Status = $Status
Timestamp = Get-Date
}
$results += $result
}
catch {
Write-Error "Failed to process $computer: $_"
}
}
}
}
end {
Write-Verbose "Completed processing $($results.Count) items"
return $results
}
}
# Usage examples
Get-AdvancedExample -ComputerName "Server01" -Verbose
Get-AdvancedExample -ComputerName "Server01" -Status "Running" -WhatIf
"Server01","Server02" | Get-AdvancedExample -Detailed
Parameter Validation Attributes:
| Attribute | Purpose | Example |
|---|---|---|
| ValidateNotNullOrEmpty | Ensures parameter has a value | [ValidateNotNullOrEmpty()][string]$Name |
| ValidateSet | Restricts to specific values | [ValidateSet("A","B","C")][string]$Choice |
| ValidateRange | Numeric value must be in range | [ValidateRange(1,100)][int]$Count |
| ValidateLength | String length must be in range | [ValidateLength(5,20)][string]$Username |
| ValidatePattern | Must match regex pattern | [ValidatePattern("^\d{3}-\d{4}$")][string]$Phone |
| ValidateScript | Custom validation logic | [ValidateScript({Test-Path $_})][string]$Path |
| ValidateCount | Array must have specific count | [ValidateCount(1,5)][string[]]$Items |
function Get-DynamicParameterExample {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Computer","User","Service")]
[string]$Type
)
DynamicParam {
# Create parameter dictionary
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Add dynamic parameter based on Type
if ($Type -eq "Computer") {
$paramName = "ComputerName"
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$paramAttribute = New-Object System.Management.Automation.ParameterAttribute
$paramAttribute.Mandatory = $true
$attributeCollection.Add($paramAttribute)
$validateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute("Server01","Server02","Server03")
$attributeCollection.Add($validateSetAttribute)
$runtimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($paramName, [string], $attributeCollection)
$paramDictionary.Add($paramName, $runtimeParameter)
}
elseif ($Type -eq "Service") {
$paramName = "ServiceName"
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$paramAttribute = New-Object System.Management.Automation.ParameterAttribute
$paramAttribute.Mandatory = $true
$attributeCollection.Add($paramAttribute)
$runtimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($paramName, [string], $attributeCollection)
$paramDictionary.Add($paramName, $runtimeParameter)
}
return $paramDictionary
}
process {
Write-Host "Type: $Type"
if ($Type -eq "Computer") {
Write-Host "Computer: $($PSBoundParameters['ComputerName'])"
}
elseif ($Type -eq "Service") {
Write-Host "Service: $($PSBoundParameters['ServiceName'])"
}
}
}
# Usage - Notice how available parameters change based on Type
Get-DynamicParameterExample -Type Computer -ComputerName "Server01"
Get-DynamicParameterExample -Type Service -ServiceName "Spooler"
17.3 Regular Expressions (Regex)
# Match operator -match
"Hello World" -match "World" # Returns True
"[email protected]" -match "\w+@\w+\.\w+" # Email pattern
# Case-sensitive match
"Hello" -cmatch "hello" # Returns False
"Hello" -match "hello" # Returns True (case-insensitive)
# Extract matches using $Matches
$text = "My phone is 555-1234"
if ($text -match "(\d{3})-(\d{4})") {
Write-Host "Full match: $($Matches[0])"
Write-Host "Area code: $($Matches[1])"
Write-Host "Number: $($Matches[2])"
}
# Replace using -replace
$text = "Hello World"
$text -replace "World", "PowerShell" # Returns "Hello PowerShell"
# Remove all digits
"abc123def456" -replace "\d", "" # Returns "abcdef"
# Multiple replacements
$text = "The quick brown fox"
$text -replace "quick|brown", "REPLACED"
Common Regex Patterns:
| Pattern | Matches | Example |
|---|---|---|
| \d | Any digit (0-9) | "abc123" -match "\d+" β 123 |
| \w | Word character (a-z, A-Z, 0-9, _) | "user_123" -match "\w+" β user_123 |
| \s | Whitespace (space, tab, newline) | "a b" -match "\s" β True |
| ^ | Start of string | "Hello" -match "^H" β True |
| $ | End of string | "Hello" -match "o$" β True |
| . | Any single character | "cat" -match "c.t" β True |
| * | Zero or more times | "goooal" -match "go*al" β True |
| + | One or more times | "goal" -match "go+al" β True |
| ? | Zero or one time | "color" -match "colou?r" β True |
| [abc] | Any character in brackets | "cat" -match "[cb]at" β True |
| [^abc] | Any character NOT in brackets | "rat" -match "[^cb]at" β True |
| {n} | Exactly n times | "1234" -match "\d{4}" β True |
| {n,m} | Between n and m times | "12345" -match "\d{3,5}" β True |
# Validate email address
function Test-EmailAddress {
param([string]$Email)
$Email -match "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"
}
Test-EmailAddress "[email protected]" # True
Test-EmailAddress "invalid.email" # False
# Validate IP address
function Test-IPAddress {
param([string]$IP)
$IP -match "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
}
Test-IPAddress "192.168.1.1" # True
Test-IPAddress "999.999.999.999" # False
# Extract phone numbers from text
$text = "Call me at 555-1234 or 555-5678"
$phoneNumbers = [regex]::Matches($text, "\d{3}-\d{4}")
foreach ($match in $phoneNumbers) {
Write-Host "Found: $($match.Value)"
}
# Parse log file entries
$logLine = "2024-01-15 14:30:45 ERROR: Connection failed"
if ($logLine -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+): (.+)$") {
$date = $Matches[1]
$time = $Matches[2]
$level = $Matches[3]
$message = $Matches[4]
Write-Host "Date: $date"
Write-Host "Time: $time"
Write-Host "Level: $level"
Write-Host "Message: $message"
}
# Clean up text - remove special characters
$text = "Hello! @World# 123$"
$cleaned = $text -replace "[^\w\s]", ""
Write-Host $cleaned # "Hello World 123"
# Extract version numbers
$text = "Version 3.14.159 is available"
if ($text -match "Version (\d+\.\d+\.\d+)") {
Write-Host "Version found: $($Matches[1])"
}
17.4 Background Jobs and Parallel Processing
# Start a simple background job
$job = Start-Job -ScriptBlock {
Get-Process | Where-Object {$_.CPU -gt 10}
}
# Check job status
Get-Job
# Wait for job to complete
Wait-Job -Job $job
# Get job results
$results = Receive-Job -Job $job
# Clean up
Remove-Job -Job $job
# Start multiple jobs
$computers = "Server01", "Server02", "Server03"
$jobs = @()
foreach ($computer in $computers) {
$jobs += Start-Job -ScriptBlock {
param($comp)
Test-Connection -ComputerName $comp -Count 2 -Quiet
} -ArgumentList $computer
}
# Wait for all jobs
$jobs | Wait-Job
# Get all results
$jobs | Receive-Job
# Clean up all jobs
$jobs | Remove-Job
# Modern parallel processing (PowerShell 7+)
$computers = 1..10 | ForEach-Object { "Server$_" }
# Process in parallel with throttle limit
$results = $computers | ForEach-Object -Parallel {
[PSCustomObject]@{
ComputerName = $_
Online = Test-Connection -ComputerName $_ -Count 1 -Quiet
Timestamp = Get-Date
}
} -ThrottleLimit 5
$results | Format-Table -AutoSize
# Parallel file processing
$files = Get-ChildItem -Path "C:\Logs" -Filter "*.log"
$files | ForEach-Object -Parallel {
$content = Get-Content -Path $_.FullName
$errorCount = ($content | Select-String -Pattern "ERROR").Count
[PSCustomObject]@{
FileName = $_.Name
ErrorCount = $errorCount
Size = $_.Length
}
} -ThrottleLimit 10
function Get-ServerHealthParallel {
param(
[string[]]$ComputerName,
[int]$ThrottleLimit = 10
)
$results = $ComputerName | ForEach-Object -Parallel {
$computer = $_
try {
# Test connectivity
$online = Test-Connection -ComputerName $computer -Count 1 -Quiet -ErrorAction Stop
if ($online) {
# Get system info
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $computer -ErrorAction Stop
$disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $computer -ErrorAction Stop |
Select-Object -First 1
$freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
[PSCustomObject]@{
ComputerName = $computer
Status = "Online"
OS = $os.Caption
LastBoot = $os.LastBootUpTime
TotalRAM_GB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
DiskFreePercent = $freePercent
Error = $null
}
}
else {
[PSCustomObject]@{
ComputerName = $computer
Status = "Offline"
Error = "Cannot reach computer"
}
}
}
catch {
[PSCustomObject]@{
ComputerName = $computer
Status = "Error"
Error = $_.Exception.Message
}
}
} -ThrottleLimit $ThrottleLimit
return $results
}
# Usage
$servers = 1..20 | ForEach-Object { "Server$_" }
$health = Get-ServerHealthParallel -ComputerName $servers -ThrottleLimit 10
$health | Format-Table -AutoSize
17.5 PowerShell Classes (Object-Oriented Programming)
# Define a class
class Server {
# Properties
[string]$Name
[string]$IPAddress
[string]$OS
[bool]$IsOnline
# Constructor
Server([string]$name, [string]$ip) {
$this.Name = $name
$this.IPAddress = $ip
$this.IsOnline = $false
}
# Methods
[bool] TestConnection() {
$this.IsOnline = Test-Connection -ComputerName $this.IPAddress -Count 1 -Quiet
return $this.IsOnline
}
[string] GetInfo() {
return "Server: $($this.Name) - IP: $($this.IPAddress) - Online: $($this.IsOnline)"
}
}
# Create instances
$server1 = [Server]::new("WebServer01", "192.168.1.10")
$server2 = [Server]::new("DBServer01", "192.168.1.20")
# Use methods
$server1.TestConnection()
Write-Host $server1.GetInfo()
# Access properties
Write-Host "Server name: $($server1.Name)"
Write-Host "Is online: $($server1.IsOnline)"
# Base class
class Computer {
[string]$Name
[string]$IPAddress
[datetime]$LastChecked
Computer([string]$name, [string]$ip) {
$this.Name = $name
$this.IPAddress = $ip
}
[bool] Ping() {
$this.LastChecked = Get-Date
return Test-Connection -ComputerName $this.IPAddress -Count 1 -Quiet
}
[string] ToString() {
return "$($this.Name) ($($this.IPAddress))"
}
}
# Derived class
class WindowsServer : Computer {
[string]$Domain
[string[]]$Roles
[int]$RAM_GB
WindowsServer([string]$name, [string]$ip, [string]$domain) : base($name, $ip) {
$this.Domain = $domain
$this.Roles = @()
}
[void] AddRole([string]$role) {
$this.Roles += $role
}
[string] GetFullName() {
return "$($this.Name).$($this.Domain)"
}
[hashtable] GetInfo() {
return @{
Name = $this.Name
FullName = $this.GetFullName()
IPAddress = $this.IPAddress
Domain = $this.Domain
Roles = $this.Roles -join ", "
RAM_GB = $this.RAM_GB
LastChecked = $this.LastChecked
}
}
}
# Usage
$server = [WindowsServer]::new("WEB01", "192.168.1.10", "contoso.com")
$server.RAM_GB = 16
$server.AddRole("IIS")
$server.AddRole("WebServer")
if ($server.Ping()) {
Write-Host "Server is online"
$info = $server.GetInfo()
$info.GetEnumerator() | ForEach-Object {
Write-Host "$($_.Key): $($_.Value)"
}
}
class UserAccount {
[string]$Username
[string]$FullName
[string]$Email
[datetime]$CreatedDate
[bool]$IsActive
[string[]]$Groups
# Constructor
UserAccount([string]$username, [string]$fullName, [string]$email) {
$this.Username = $username
$this.FullName = $fullName
$this.Email = $email
$this.CreatedDate = Get-Date
$this.IsActive = $true
$this.Groups = @()
}
# Validate email format
hidden [bool] ValidateEmail([string]$email) {
return $email -match "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"
}
# Add user to group
[void] AddToGroup([string]$groupName) {
if ($this.Groups -notcontains $groupName) {
$this.Groups += $groupName
Write-Host "Added $($this.Username) to group: $groupName"
}
else {
Write-Warning "$($this.Username) is already in group: $groupName"
}
}
# Remove user from group
[void] RemoveFromGroup([string]$groupName) {
if ($this.Groups -contains $groupName) {
$this.Groups = $this.Groups | Where-Object { $_ -ne $groupName }
Write-Host "Removed $($this.Username) from group: $groupName"
}
else {
Write-Warning "$($this.Username) is not in group: $groupName"
}
}
# Disable account
[void] Disable() {
$this.IsActive = $false
Write-Host "Account disabled: $($this.Username)"
}
# Enable account
[void] Enable() {
$this.IsActive = $true
Write-Host "Account enabled: $($this.Username)"
}
# Get account summary
[PSCustomObject] GetSummary() {
return [PSCustomObject]@{
Username = $this.Username
FullName = $this.FullName
Email = $this.Email
CreatedDate = $this.CreatedDate
IsActive = $this.IsActive
Groups = $this.Groups -join ", "
DaysSinceCreation = ((Get-Date) - $this.CreatedDate).Days
}
}
}
# Usage
$user = [UserAccount]::new("jdoe", "John Doe", "[email protected]")
$user.AddToGroup("Developers")
$user.AddToGroup("RemoteUsers")
$user.AddToGroup("VPN-Access")
Write-Host "`nUser Summary:"
$user.GetSummary() | Format-List
$user.Disable()
$user.Enable()
$user.RemoveFromGroup("VPN-Access")
17.6 Advanced Scripting Techniques
# Without splatting (hard to read)
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName "Server01" -ErrorAction Stop
# With splatting (clean and readable)
$params = @{
ClassName = "Win32_LogicalDisk"
Filter = "DriveType=3"
ComputerName = "Server01"
ErrorAction = "Stop"
}
Get-CimInstance @params
# Conditional splatting
$params = @{
Path = "C:\Logs"
Filter = "*.log"
}
if ($Recurse) {
$params.Add("Recurse", $true)
}
Get-ChildItem @params
# Combining multiple splat hashtables
$commonParams = @{
ErrorAction = "Stop"
Verbose = $true
}
$specificParams = @{
ComputerName = "Server01"
ClassName = "Win32_Service"
}
Get-CimInstance @commonParams @specificParams
# Here-string (expandable - variables are replaced)
$name = "John"
$message = @"
Hello $name,
This is a multi-line message.
It preserves formatting and indentation.
Variables like $name are expanded.
Regards,
Admin Team
"@
Write-Host $message
# Here-string literal (non-expandable - variables are NOT replaced)
$template = @'
Dear {Name},
Your account {Username} has been created.
Password: {Password}
Please change your password on first login.
'@
# Replace placeholders
$email = $template -replace "{Name}", "John Doe" `
-replace "{Username}", "jdoe" `
-replace "{Password}", "TempPass123"
Write-Host $email
# SQL query example
$query = @"
SELECT
u.Username,
u.Email,
u.CreatedDate
FROM Users u
WHERE u.IsActive = 1
AND u.Department = 'IT'
ORDER BY u.CreatedDate DESC
"@
# HTML template
$htmlReport = @"
Server Report
Server Status Report
Generated: $(Get-Date)
Server Status Uptime
"@
# Define a script block
$scriptBlock = {
param($ComputerName)
$os = Get-CimInstance -ClassName Win32_OperatingSystem
[PSCustomObject]@{
ComputerName = $ComputerName
OS = $os.Caption
FreeMemory_GB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
LastBoot = $os.LastBootUpTime
}
}
# Execute locally
& $scriptBlock -ComputerName $env:COMPUTERNAME
# Execute remotely
Invoke-Command -ComputerName "Server01" -ScriptBlock $scriptBlock -ArgumentList "Server01"
# Store script block in variable and reuse
$getServiceInfo = {
param($ServiceName)
Get-Service -Name $ServiceName | Select-Object Name, Status, StartType
}
# Use multiple times
& $getServiceInfo -ServiceName "Spooler"
& $getServiceInfo -ServiceName "W32Time"
# Script block with closure (accessing parent scope)
$threshold = 80
$checkDiskSpace = {
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$freePercent = ($_.FreeSpace / $_.Size) * 100
if ($freePercent -lt $using:threshold) {
Write-Warning "Drive $($_.DeviceID) is low on space: $([math]::Round($freePercent,2))%"
}
}
}
Invoke-Command -ComputerName "Server01" -ScriptBlock $checkDiskSpace
17.7 Performance Optimization Techniques
# SLOW: Using += to build arrays
Measure-Command {
$array = @()
1..10000 | ForEach-Object {
$array += $_
}
}
# FAST: Using ArrayList
Measure-Command {
$arrayList = [System.Collections.ArrayList]@()
1..10000 | ForEach-Object {
[void]$arrayList.Add($_)
}
}
# FASTEST: Using Generic List
Measure-Command {
$list = [System.Collections.Generic.List[int]]::new()
1..10000 | ForEach-Object {
$list.Add($_)
}
}
# SLOW: Multiple Where-Object filters
Measure-Command {
Get-Process |
Where-Object {$_.CPU -gt 10} |
Where-Object {$_.WorkingSet -gt 100MB}
}
# FAST: Combined filter
Measure-Command {
Get-Process | Where-Object {$_.CPU -gt 10 -and $_.WorkingSet -gt 100MB}
}
# SLOW: ForEach-Object for simple operations
Measure-Command {
1..10000 | ForEach-Object { $_ * 2 }
}
# FAST: ForEach method
Measure-Command {
(1..10000).ForEach({ $_ * 2 })
}
# SLOW: String concatenation in loop
Measure-Command {
$result = ""
1..1000 | ForEach-Object {
$result += "Line $_`n"
}
}
# FAST: StringBuilder
Measure-Command {
$sb = [System.Text.StringBuilder]::new()
1..1000 | ForEach-Object {
[void]$sb.AppendLine("Line $_")
}
$result = $sb.ToString()
}
Performance Best Practices:
- Use .NET collections: ArrayList or Generic.List instead of += for arrays
- Filter early: Use -Filter parameter instead of Where-Object when possible
- Combine filters: Use -and/-or instead of multiple Where-Object
- Use .ForEach() method: Faster than ForEach-Object for simple operations
- StringBuilder for strings: Use StringBuilder for concatenating many strings
- Select properties early: Use Select-Object to reduce data size early
- Avoid Get-WmiObject: Use Get-CimInstance (faster)
- Use -ReadCount: Process files in batches with Get-Content
- Parallel processing: Use ForEach-Object -Parallel for independent operations
- Measure performance: Use Measure-Command to test different approaches
17.8 Advanced Error Handling
# Trap specific error types
function Test-AdvancedErrorHandling {
# Trap for specific exception type
trap [System.IO.FileNotFoundException] {
Write-Host "File not found: $($_.Exception.Message)" -ForegroundColor Red
continue # Continue execution
}
trap [System.UnauthorizedAccessException] {
Write-Host "Access denied: $($_.Exception.Message)" -ForegroundColor Red
break # Stop execution
}
# General trap for all other errors
trap {
Write-Host "Unexpected error: $($_.Exception.Message)" -ForegroundColor Yellow
continue
}
# Code that might throw errors
Get-Content "C:\NonExistent.txt"
Get-Content "C:\Windows\System32\config\SAM" # Access denied
}
# Custom exception class
class CustomException : System.Exception {
[string]$CustomMessage
[datetime]$Timestamp
CustomException([string]$message) : base($message) {
$this.CustomMessage = $message
$this.Timestamp = Get-Date
}
}
# Throw custom exception
function Test-CustomException {
param([int]$Value)
if ($Value -lt 0) {
throw [CustomException]::new("Value cannot be negative")
}
Write-Host "Value is valid: $Value"
}
try {
Test-CustomException -Value -5
}
catch [CustomException] {
Write-Host "Custom exception caught!"
Write-Host "Message: $($_.Exception.CustomMessage)"
Write-Host "Time: $($_.Exception.Timestamp)"
}
17.9 Chapter Summary
Key Takeaways:
- β Advanced Functions: Use [CmdletBinding()], parameter validation, and pipeline input
- β Regular Expressions: Master -match, -replace, and common patterns for text processing
- β Background Jobs: Use Start-Job or ForEach-Object -Parallel for concurrent operations
- β PowerShell Classes: Create reusable object-oriented code with classes and inheritance
- β Splatting: Use @{} hashtables for cleaner parameter passing
- β Here-Strings: Use @" "@ for multi-line text with formatting
- β Script Blocks: Store and reuse code with { } script blocks
- β Performance: Use .NET collections, filter early, and measure with Measure-Command
- β Error Handling: Use trap, custom exceptions, and specific exception types
- β Best Practices: Write professional, maintainable, and efficient code
Chapter 18: Best Practices and Real-World Projects
18.1 Introduction to Production PowerShell
Moving from learning PowerShell to using it in production requires understanding best practices, proper error handling, logging, and creating maintainable, reusable code. This chapter provides production-ready examples and guidelines.
Production PowerShell Checklist:
- β Proper error handling and logging
- β Input validation and parameter checking
- β Comment-based help documentation
- β Modular and reusable code
- β Security considerations
- β Performance optimization
- β Testing and validation
- β Version control and change management
18.2 PowerShell Coding Standards
Naming Conventions:
| Item | Convention | Example |
|---|---|---|
| Functions | Verb-Noun (PascalCase) | Get-UserInfo, Set-SystemConfig |
| Parameters | PascalCase | -ComputerName, -FilePath |
| Variables | camelCase or PascalCase | $userName, $totalCount |
| Constants | UPPERCASE | $MAX_RETRIES, $DEFAULT_TIMEOUT |
| Script Files | PascalCase.ps1 | BackupDatabase.ps1 |
| Module Files | PascalCase.psm1 | SystemTools.psm1 |
function Verb-Noun {
<#
.SYNOPSIS
Brief description of what the function does.
.DESCRIPTION
Detailed description of the function's purpose and behavior.
.PARAMETER ParameterName
Description of the parameter.
.EXAMPLE
Verb-Noun -ParameterName "Value"
Description of what this example does.
.EXAMPLE
Verb-Noun -ParameterName "Value" -Verbose
Another example with verbose output.
.NOTES
Author: Your Name
Date: 2024-01-01
Version: 1.0
.LINK
https://documentation-url.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
HelpMessage="Enter the parameter value")]
[ValidateNotNullOrEmpty()]
[string]$ParameterName,
[Parameter(Mandatory=$false)]
[ValidateRange(1,100)]
[int]$Timeout = 30
)
begin {
Write-Verbose "Starting $($MyInvocation.MyCommand)"
# Initialization code
}
process {
try {
if ($PSCmdlet.ShouldProcess($ParameterName, "Perform action")) {
# Main logic here
Write-Verbose "Processing $ParameterName"
# Your code here
}
}
catch {
Write-Error "Error processing $ParameterName: $_"
throw
}
}
end {
Write-Verbose "Completed $($MyInvocation.MyCommand)"
# Cleanup code
}
}
18.3 Logging Best Practices
function Write-Log {
<#
.SYNOPSIS
Writes log messages to file and console with timestamps and severity levels.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[Parameter(Mandatory=$false)]
[ValidateSet('INFO','WARNING','ERROR','DEBUG')]
[string]$Level = 'INFO',
[Parameter(Mandatory=$false)]
[string]$LogPath = "$env:TEMP\PowerShell_$(Get-Date -Format 'yyyyMMdd').log"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
# Write to file
try {
Add-Content -Path $LogPath -Value $logEntry -ErrorAction Stop
}
catch {
Write-Warning "Failed to write to log file: $_"
}
# Write to console with color
$color = switch ($Level) {
'INFO' { 'White' }
'WARNING' { 'Yellow' }
'ERROR' { 'Red' }
'DEBUG' { 'Gray' }
}
Write-Host $logEntry -ForegroundColor $color
}
# Usage examples
Write-Log -Message "Script started" -Level INFO
Write-Log -Message "Processing user data" -Level DEBUG
Write-Log -Message "Disk space low" -Level WARNING
Write-Log -Message "Failed to connect to server" -Level ERROR
18.4 Configuration Management
# config.json file content:
<#
{
"General": {
"LogPath": "C:\Logs",
"MaxRetries": 3,
"Timeout": 30
},
"Email": {
"SmtpServer": "smtp.company.com",
"From": "[email protected]",
"To": ["[email protected]", "[email protected]"]
},
"Servers": [
"Server01",
"Server02",
"Server03"
]
}
#>
# Load configuration
function Get-ScriptConfig {
param(
[string]$ConfigPath = "$PSScriptRoot\config.json"
)
if (-not (Test-Path $ConfigPath)) {
throw "Configuration file not found: $ConfigPath"
}
try {
$config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
return $config
}
catch {
throw "Failed to load configuration: $_"
}
}
# Use configuration
$config = Get-ScriptConfig
$logPath = $config.General.LogPath
$servers = $config.Servers
$smtpServer = $config.Email.SmtpServer
Write-Host "Log Path: $logPath"
Write-Host "Servers: $($servers -join ', ')"
Write-Host "SMTP Server: $smtpServer"
18.5 Production Error Handling Patterns
function Invoke-SafeOperation {
param(
[string]$ComputerName,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 5
)
$attempt = 0
$success = $false
while (-not $success -and $attempt -lt $MaxRetries) {
$attempt++
try {
Write-Log "Attempt $attempt of $MaxRetries for $ComputerName" -Level INFO
# Test connectivity first
if (-not (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)) {
throw "Computer $ComputerName is not reachable"
}
# Perform operation
$result = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
Get-Service | Where-Object {$_.Status -eq 'Running'}
} -ErrorAction Stop
Write-Log "Successfully retrieved services from $ComputerName" -Level INFO
$success = $true
return $result
}
catch [System.Management.Automation.Remoting.PSRemotingTransportException] {
Write-Log "Remoting error for $ComputerName : $_" -Level WARNING
if ($attempt -lt $MaxRetries) {
Write-Log "Retrying in $RetryDelaySeconds seconds..." -Level INFO
Start-Sleep -Seconds $RetryDelaySeconds
}
}
catch [System.Net.NetworkInformation.PingException] {
Write-Log "Network error for $ComputerName : $_" -Level ERROR
break # Don't retry network errors
}
catch {
Write-Log "Unexpected error for $ComputerName : $_" -Level ERROR
Write-Log "Error Type: $($_.Exception.GetType().FullName)" -Level DEBUG
if ($attempt -lt $MaxRetries) {
Start-Sleep -Seconds $RetryDelaySeconds
}
}
}
if (-not $success) {
Write-Log "Failed to complete operation for $ComputerName after $MaxRetries attempts" -Level ERROR
return $null
}
}
# Usage
$result = Invoke-SafeOperation -ComputerName "Server01" -MaxRetries 3
18.6 Real-World Project 1: Comprehensive Server Health Check
<#
.SYNOPSIS
Comprehensive server health check with email reporting.
.DESCRIPTION
Checks multiple servers for disk space, CPU, memory, services, and event log errors.
Generates HTML report and sends email if issues are found.
.PARAMETER ComputerName
Array of computer names to check.
.PARAMETER DiskSpaceThreshold
Percentage of free disk space that triggers a warning (default: 20).
.PARAMETER SendEmail
Switch to enable email notifications.
.EXAMPLE
.\ServerHealthCheck.ps1 -ComputerName "Server01","Server02" -SendEmail
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[int]$DiskSpaceThreshold = 20,
[Parameter(Mandatory=$false)]
[switch]$SendEmail,
[Parameter(Mandatory=$false)]
[string]$SmtpServer = "smtp.company.com",
[Parameter(Mandatory=$false)]
[string]$EmailFrom = "[email protected]",
[Parameter(Mandatory=$false)]
[string[]]$EmailTo = @("[email protected]")
)
# Initialize
$ErrorActionPreference = 'Continue'
$reportPath = "$PSScriptRoot\Reports\HealthCheck_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
$results = @()
# Ensure report directory exists
$reportDir = Split-Path $reportPath -Parent
if (-not (Test-Path $reportDir)) {
New-Item -Path $reportDir -ItemType Directory -Force | Out-Null
}
Write-Host "Starting health check for $($ComputerName.Count) servers..." -ForegroundColor Cyan
foreach ($computer in $ComputerName) {
Write-Host "`nChecking $computer..." -ForegroundColor Yellow
$serverHealth = [PSCustomObject]@{
ServerName = $computer
Status = 'Unknown'
Uptime = $null
CPUUsage = $null
MemoryUsedPercent = $null
DiskInfo = @()
CriticalServices = @()
RecentErrors = 0
Issues = @()
}
try {
# Test connectivity
if (-not (Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
throw "Server is not reachable"
}
# Create CIM session
$session = New-CimSession -ComputerName $computer -ErrorAction Stop
# Get OS information
$os = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session
$lastBoot = $os.LastBootUpTime
$uptime = (Get-Date) - $lastBoot
$serverHealth.Uptime = "$($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m"
# Get CPU usage (average over 5 seconds)
$cpu = Get-CimInstance -ClassName Win32_Processor -CimSession $session
$serverHealth.CPUUsage = [math]::Round($cpu.LoadPercentage, 2)
if ($serverHealth.CPUUsage -gt 80) {
$serverHealth.Issues += "High CPU usage: $($serverHealth.CPUUsage)%"
}
# Get memory usage
$totalMemory = $os.TotalVisibleMemorySize
$freeMemory = $os.FreePhysicalMemory
$usedMemory = $totalMemory - $freeMemory
$memoryPercent = [math]::Round(($usedMemory / $totalMemory) * 100, 2)
$serverHealth.MemoryUsedPercent = $memoryPercent
if ($memoryPercent -gt 90) {
$serverHealth.Issues += "High memory usage: $memoryPercent%"
}
# Get disk space
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -CimSession $session
foreach ($disk in $disks) {
$freePercent = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
$diskInfo = [PSCustomObject]@{
Drive = $disk.DeviceID
SizeGB = [math]::Round($disk.Size / 1GB, 2)
FreeGB = [math]::Round($disk.FreeSpace / 1GB, 2)
FreePercent = $freePercent
}
$serverHealth.DiskInfo += $diskInfo
if ($freePercent -lt $DiskSpaceThreshold) {
$serverHealth.Issues += "Low disk space on $($disk.DeviceID): $freePercent% free"
}
}
# Check critical services
$criticalServices = @('W32Time', 'Winmgmt', 'RpcSs', 'Dhcp', 'Dnscache')
$services = Get-CimInstance -ClassName Win32_Service -CimSession $session |
Where-Object {$_.Name -in $criticalServices}
foreach ($service in $services) {
if ($service.State -ne 'Running') {
$serverHealth.CriticalServices += "$($service.DisplayName) is $($service.State)"
$serverHealth.Issues += "Service $($service.DisplayName) is not running"
}
}
# Check recent errors in System event log (last 24 hours)
$yesterday = (Get-Date).AddDays(-1)
$errors = Get-WinEvent -ComputerName $computer -FilterHashtable @{
LogName = 'System'
Level = 2 # Error
StartTime = $yesterday
} -ErrorAction SilentlyContinue
$serverHealth.RecentErrors = $errors.Count
if ($errors.Count -gt 10) {
$serverHealth.Issues += "$($errors.Count) errors in System log (last 24h)"
}
# Set overall status
$serverHealth.Status = if ($serverHealth.Issues.Count -eq 0) { 'Healthy' } else { 'Warning' }
# Clean up
Remove-CimSession -CimSession $session
Write-Host " Status: $($serverHealth.Status)" -ForegroundColor $(if($serverHealth.Status -eq 'Healthy'){'Green'}else{'Yellow'})
}
catch {
$serverHealth.Status = 'Error'
$serverHealth.Issues += "Failed to retrieve data: $_"
Write-Host " Status: Error - $_" -ForegroundColor Red
}
$results += $serverHealth
}
# Generate HTML Report
$htmlHead = @"
"@
$htmlBody = "Server Health Check Report
"
$htmlBody += "Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
"
foreach ($server in $results) {
$statusClass = $server.Status.ToLower()
$htmlBody += "$($server.ServerName) - $($server.Status)
"
$htmlBody += ""
$htmlBody += "Metric Value "
$htmlBody += "Uptime $($server.Uptime) "
$htmlBody += "CPU Usage $($server.CPUUsage)% "
$htmlBody += "Memory Used $($server.MemoryUsedPercent)% "
$htmlBody += "Recent Errors $($server.RecentErrors) "
$htmlBody += "
"
if ($server.DiskInfo.Count -gt 0) {
$htmlBody += "Disk Space
"
$htmlBody += "Drive Size (GB) Free (GB) Free % "
foreach ($disk in $server.DiskInfo) {
$htmlBody += "$($disk.Drive) $($disk.SizeGB) $($disk.FreeGB) $($disk.FreePercent)% "
}
$htmlBody += "
"
}
if ($server.Issues.Count -gt 0) {
$htmlBody += "Issues Found:
"
foreach ($issue in $server.Issues) {
$htmlBody += "- $issue
"
}
$htmlBody += "
"
}
}
$htmlReport = $htmlHead + $htmlBody
# Save report
$htmlReport | Out-File -FilePath $reportPath -Encoding UTF8
Write-Host "`nReport saved to: $reportPath" -ForegroundColor Green
# Send email if requested and issues found
$issueCount = ($results | Where-Object {$_.Issues.Count -gt 0}).Count
if ($SendEmail -and $issueCount -gt 0) {
try {
$emailParams = @{
SmtpServer = $SmtpServer
From = $EmailFrom
To = $EmailTo
Subject = "Server Health Check Alert - $issueCount server(s) with issues"
Body = $htmlReport
BodyAsHtml = $true
}
Send-MailMessage @emailParams
Write-Host "Email notification sent to $($EmailTo -join ', ')" -ForegroundColor Green
}
catch {
Write-Warning "Failed to send email: $_"
}
}
Write-Host "`nHealth check complete!" -ForegroundColor Cyan
Write-Host "Servers checked: $($results.Count)" -ForegroundColor White
Write-Host "Healthy: $(($results | Where-Object {$_.Status -eq 'Healthy'}).Count)" -ForegroundColor Green
Write-Host "Issues: $issueCount" -ForegroundColor $(if($issueCount -gt 0){'Yellow'}else{'Green'})
18.7 Real-World Project 2: Automated Backup System
<#
.SYNOPSIS
Automated backup system with rotation and email notifications.
.DESCRIPTION
Backs up specified folders, maintains retention policy, and sends email reports.
Supports compression, verification, and automatic cleanup of old backups.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$SourcePaths,
[Parameter(Mandatory=$true)]
[string]$BackupDestination,
[Parameter(Mandatory=$false)]
[int]$RetentionDays = 30,
[Parameter(Mandatory=$false)]
[switch]$Compress,
[Parameter(Mandatory=$false)]
[switch]$SendEmail,
[Parameter(Mandatory=$false)]
[string]$SmtpServer = "smtp.company.com",
[Parameter(Mandatory=$false)]
[string]$EmailFrom = "[email protected]",
[Parameter(Mandatory=$false)]
[string[]]$EmailTo = @("[email protected]")
)
# Initialize logging
$logPath = "$BackupDestination\Logs\Backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$logDir = Split-Path $logPath -Parent
if (-not (Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
}
function Write-BackupLog {
param([string]$Message, [string]$Level = 'INFO')
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $logPath -Value $logEntry
$color = switch ($Level) {
'INFO' { 'White' }
'SUCCESS' { 'Green' }
'WARNING' { 'Yellow' }
'ERROR' { 'Red' }
}
Write-Host $logEntry -ForegroundColor $color
}
# Start backup process
Write-BackupLog "=== Backup Process Started ===" -Level INFO
Write-BackupLog "Source Paths: $($SourcePaths -join ', ')" -Level INFO
Write-BackupLog "Destination: $BackupDestination" -Level INFO
Write-BackupLog "Retention: $RetentionDays days" -Level INFO
$backupResults = @()
$totalSize = 0
$successCount = 0
$failCount = 0
# Create backup destination if it doesn't exist
if (-not (Test-Path $BackupDestination)) {
New-Item -Path $BackupDestination -ItemType Directory -Force | Out-Null
Write-BackupLog "Created backup destination: $BackupDestination" -Level INFO
}
# Process each source path
foreach ($sourcePath in $SourcePaths) {
$sourceName = Split-Path $sourcePath -Leaf
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$backupName = "$sourceName`_$timestamp"
$destinationPath = Join-Path $BackupDestination $backupName
Write-BackupLog "Processing: $sourcePath" -Level INFO
$result = [PSCustomObject]@{
SourcePath = $sourcePath
BackupName = $backupName
DestinationPath = $destinationPath
Status = 'Unknown'
SizeMB = 0
Duration = $null
ErrorMessage = $null
}
try {
# Validate source path
if (-not (Test-Path $sourcePath)) {
throw "Source path does not exist: $sourcePath"
}
$startTime = Get-Date
if ($Compress) {
# Compress backup
$zipPath = "$destinationPath.zip"
Write-BackupLog "Creating compressed backup: $zipPath" -Level INFO
Compress-Archive -Path $sourcePath -DestinationPath $zipPath -CompressionLevel Optimal -Force
$backupSize = (Get-Item $zipPath).Length
$result.DestinationPath = $zipPath
}
else {
# Copy without compression
Write-BackupLog "Copying to: $destinationPath" -Level INFO
Copy-Item -Path $sourcePath -Destination $destinationPath -Recurse -Force
$backupSize = (Get-ChildItem -Path $destinationPath -Recurse |
Measure-Object -Property Length -Sum).Sum
}
$endTime = Get-Date
$duration = $endTime - $startTime
$result.Status = 'Success'
$result.SizeMB = [math]::Round($backupSize / 1MB, 2)
$result.Duration = "$($duration.Minutes)m $($duration.Seconds)s"
$totalSize += $result.SizeMB
$successCount++
Write-BackupLog "Backup completed: $backupName ($($result.SizeMB) MB in $($result.Duration))" -Level SUCCESS
}
catch {
$result.Status = 'Failed'
$result.ErrorMessage = $_.Exception.Message
$failCount++
Write-BackupLog "Backup failed for $sourcePath : $_" -Level ERROR
}
$backupResults += $result
}
# Cleanup old backups based on retention policy
Write-BackupLog "`n=== Cleaning up old backups ===" -Level INFO
try {
$cutoffDate = (Get-Date).AddDays(-$RetentionDays)
$oldBackups = Get-ChildItem -Path $BackupDestination -Recurse |
Where-Object {$_.LastWriteTime -lt $cutoffDate}
if ($oldBackups) {
Write-BackupLog "Found $($oldBackups.Count) old backup(s) to remove" -Level INFO
foreach ($oldBackup in $oldBackups) {
try {
Remove-Item -Path $oldBackup.FullName -Recurse -Force
Write-BackupLog "Removed old backup: $($oldBackup.Name)" -Level INFO
}
catch {
Write-BackupLog "Failed to remove $($oldBackup.Name): $_" -Level WARNING
}
}
}
else {
Write-BackupLog "No old backups to remove" -Level INFO
}
}
catch {
Write-BackupLog "Error during cleanup: $_" -Level ERROR
}
# Generate summary
Write-BackupLog "`n=== Backup Summary ===" -Level INFO
Write-BackupLog "Total backups attempted: $($backupResults.Count)" -Level INFO
Write-BackupLog "Successful: $successCount" -Level SUCCESS
Write-BackupLog "Failed: $failCount" -Level $(if($failCount -gt 0){'ERROR'}else{'INFO'})
Write-BackupLog "Total size: $([math]::Round($totalSize / 1024, 2)) GB" -Level INFO
# Generate HTML report
$htmlReport = @"
Backup Report
Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Total Backups: $($backupResults.Count)
Successful: $successCount
Failed: $failCount
Total Size: $([math]::Round($totalSize / 1024, 2)) GB
Backup Details
Source Path
Backup Name
Status
Size (MB)
Duration
"@
foreach ($result in $backupResults) {
$statusClass = if ($result.Status -eq 'Success') { 'success' } else { 'failed' }
$htmlReport += @"
$($result.SourcePath)
$($result.BackupName)
$($result.Status)
$($result.SizeMB)
$($result.Duration)
"@
}
$htmlReport += "
"
# Save HTML report
$reportPath = "$BackupDestination\Logs\BackupReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
$htmlReport | Out-File -FilePath $reportPath -Encoding UTF8
Write-BackupLog "Report saved to: $reportPath" -Level INFO
# Send email if requested
if ($SendEmail) {
try {
$subject = if ($failCount -gt 0) {
"Backup Report - $failCount Failed"
} else {
"Backup Report - All Successful"
}
Send-MailMessage -SmtpServer $SmtpServer -From $EmailFrom -To $EmailTo `
-Subject $subject -Body $htmlReport -BodyAsHtml
Write-BackupLog "Email report sent to $($EmailTo -join ', ')" -Level SUCCESS
}
catch {
Write-BackupLog "Failed to send email: $_" -Level ERROR
}
}
Write-BackupLog "`n=== Backup Process Completed ===" -Level INFO
18.8 Real-World Project 3: Bulk User Account Management
<#
.SYNOPSIS
Bulk user account creation from CSV file with error handling and reporting.
.DESCRIPTION
Creates Active Directory user accounts from a CSV file with the following columns:
FirstName, LastName, Username, Email, Department, Title, Manager
.PARAMETER CsvPath
Path to the CSV file containing user information.
.PARAMETER OU
Organizational Unit where users will be created.
.EXAMPLE
.\BulkUserCreation.ps1 -CsvPath "C:\Users.csv" -OU "OU=Users,DC=company,DC=com"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$CsvPath,
[Parameter(Mandatory=$true)]
[string]$OU,
[Parameter(Mandatory=$false)]
[string]$DefaultPassword = "P@ssw0rd123!",
[Parameter(Mandatory=$false)]
[switch]$SendWelcomeEmail
)
# Import Active Directory module
try {
Import-Module ActiveDirectory -ErrorAction Stop
}
catch {
Write-Error "Failed to import Active Directory module: $_"
exit 1
}
# Initialize
$logPath = "$PSScriptRoot\Logs\UserCreation_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$logDir = Split-Path $logPath -Parent
if (-not (Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
}
function Write-UserLog {
param([string]$Message, [string]$Level = 'INFO')
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $logPath -Value $logEntry
Write-Host $logEntry -ForegroundColor $(
switch($Level) {
'SUCCESS' {'Green'}
'WARNING' {'Yellow'}
'ERROR' {'Red'}
default {'White'}
}
)
}
Write-UserLog "=== Bulk User Creation Started ===" -Level INFO
Write-UserLog "CSV File: $CsvPath" -Level INFO
Write-UserLog "Target OU: $OU" -Level INFO
# Import CSV
try {
$users = Import-Csv -Path $CsvPath
Write-UserLog "Loaded $($users.Count) users from CSV" -Level INFO
}
catch {
Write-UserLog "Failed to import CSV: $_" -Level ERROR
exit 1
}
# Validate OU exists
try {
Get-ADOrganizationalUnit -Identity $OU -ErrorAction Stop | Out-Null
}
catch {
Write-UserLog "OU does not exist: $OU" -Level ERROR
exit 1
}
$results = @()
$successCount = 0
$failCount = 0
$securePassword = ConvertTo-SecureString $DefaultPassword -AsPlainText -Force
foreach ($user in $users) {
$username = $user.Username
Write-UserLog "`nProcessing user: $username" -Level INFO
$result = [PSCustomObject]@{
Username = $username
FullName = "$($user.FirstName) $($user.LastName)"
Email = $user.Email
Status = 'Unknown'
Message = ''
}
try {
# Check if user already exists
$existingUser = Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue
if ($existingUser) {
throw "User already exists: $username"
}
# Validate required fields
if ([string]::IsNullOrWhiteSpace($user.FirstName) -or
[string]::IsNullOrWhiteSpace($user.LastName)) {
throw "First name and last name are required"
}
# Create user parameters
$userParams = @{
SamAccountName = $username
UserPrincipalName = "[email protected]"
Name = "$($user.FirstName) $($user.LastName)"
GivenName = $user.FirstName
Surname = $user.LastName
DisplayName = "$($user.FirstName) $($user.LastName)"
EmailAddress = $user.Email
Department = $user.Department
Title = $user.Title
Path = $OU
AccountPassword = $securePassword
Enabled = $true
ChangePasswordAtLogon = $true
}
# Add manager if specified
if (-not [string]::IsNullOrWhiteSpace($user.Manager)) {
$manager = Get-ADUser -Filter "SamAccountName -eq '$($user.Manager)'" -ErrorAction SilentlyContinue
if ($manager) {
$userParams['Manager'] = $manager.DistinguishedName
}
}
# Create the user
New-ADUser @userParams -ErrorAction Stop
$result.Status = 'Success'
$result.Message = 'User created successfully'
$successCount++
Write-UserLog "Successfully created user: $username" -Level SUCCESS
# Send welcome email if requested
if ($SendWelcomeEmail -and -not [string]::IsNullOrWhiteSpace($user.Email)) {
try {
$emailBody = @"
Welcome to the company, $($user.FirstName)!
Your account has been created with the following details:
Username: $username
Email: $($user.Email)
Temporary Password: $DefaultPassword
Please change your password at first login.
Best regards,
IT Department
"@
Send-MailMessage -SmtpServer "smtp.company.com" `
-From "[email protected]" `
-To $user.Email `
-Subject "Welcome - Your Account Has Been Created" `
-Body $emailBody
Write-UserLog "Welcome email sent to $($user.Email)" -Level INFO
}
catch {
Write-UserLog "Failed to send welcome email: $_" -Level WARNING
}
}
}
catch {
$result.Status = 'Failed'
$result.Message = $_.Exception.Message
$failCount++
Write-UserLog "Failed to create user $username : $_" -Level ERROR
}
$results += $result
}
# Generate summary report
Write-UserLog "`n=== User Creation Summary ===" -Level INFO
Write-UserLog "Total users processed: $($results.Count)" -Level INFO
Write-UserLog "Successful: $successCount" -Level SUCCESS
Write-UserLog "Failed: $failCount" -Level $(if($failCount -gt 0){'ERROR'}else{'INFO'})
# Export results to CSV
$resultsPath = "$PSScriptRoot\Logs\UserCreationResults_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$results | Export-Csv -Path $resultsPath -NoTypeInformation
Write-UserLog "Results exported to: $resultsPath" -Level INFO
Write-UserLog "`n=== Bulk User Creation Completed ===" -Level INFO
18.9 Security Best Practices
π PowerShell Security Checklist:
- Never hardcode credentials - Use Get-Credential or secure storage
- Use execution policies - Set appropriate policy for your environment
- Sign your scripts - Use code signing certificates for production
- Validate input - Always validate user input and parameters
- Use least privilege - Run with minimum required permissions
- Encrypt sensitive data - Use SecureString and encryption
- Audit script execution - Enable PowerShell logging
- Review third-party scripts - Never run untrusted code
- Use constrained language mode - For untrusted environments
- Regular security updates - Keep PowerShell and modules updated
# WRONG - Never do this!
$password = "MyPassword123"
$username = "admin"
# RIGHT - Use Get-Credential
$credential = Get-Credential -Message "Enter your credentials"
# RIGHT - Store encrypted credentials
$credential = Get-Credential
$credential | Export-Clixml -Path "$env:USERPROFILE\cred.xml"
# Load encrypted credentials
$credential = Import-Clixml -Path "$env:USERPROFILE\cred.xml"
# Use with remoting
Invoke-Command -ComputerName Server01 -Credential $credential -ScriptBlock {
Get-Service
}
# Secure string for passwords
$securePassword = Read-Host "Enter password" -AsSecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
18.10 Performance Optimization Tips
β‘ Performance Best Practices:
| Technique | Slow | Fast |
|---|---|---|
| Array Building | $array += $item | [System.Collections.ArrayList] |
| String Building | $string += "text" | [System.Text.StringBuilder] |
| Filtering | Where-Object | .Where() method |
| Looping | ForEach-Object | foreach statement |
| WMI Queries | Get-WmiObject | Get-CimInstance |
| File Operations | Get-Content line by line | Get-Content -Raw or .NET |
18.11 Testing and Validation
# Install Pester if needed
# Install-Module -Name Pester -Force -SkipPublisherCheck
# Sample function to test
function Get-DiskSpaceInfo {
param([string]$ComputerName = $env:COMPUTERNAME)
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ComputerName $ComputerName |
Select-Object DeviceID,
@{Name='SizeGB';Expression={[math]::Round($_.Size/1GB,2)}},
@{Name='FreeGB';Expression={[math]::Round($_.FreeSpace/1GB,2)}}
}
# Pester test file: Get-DiskSpaceInfo.Tests.ps1
Describe "Get-DiskSpaceInfo" {
Context "Parameter Validation" {
It "Should accept ComputerName parameter" {
{Get-DiskSpaceInfo -ComputerName "localhost"} | Should -Not -Throw
}
It "Should use default computer name if not specified" {
$result = Get-DiskSpaceInfo
$result | Should -Not -BeNullOrEmpty
}
}
Context "Output Validation" {
It "Should return objects with required properties" {
$result = Get-DiskSpaceInfo
$result[0].PSObject.Properties.Name | Should -Contain 'DeviceID'
$result[0].PSObject.Properties.Name | Should -Contain 'SizeGB'
$result[0].PSObject.Properties.Name | Should -Contain 'FreeGB'
}
It "Should return numeric values for size properties" {
$result = Get-DiskSpaceInfo
$result[0].SizeGB | Should -BeOfType [double]
$result[0].FreeGB | Should -BeOfType [double]
}
}
}
# Run tests
Invoke-Pester -Path "Get-DiskSpaceInfo.Tests.ps1"
18.12 Chapter Summary
π Key Takeaways - Production PowerShell:
- β Follow coding standards and naming conventions
- β Implement comprehensive logging for troubleshooting
- β Use configuration files (JSON/XML) for settings
- β Implement proper error handling with try/catch and retries
- β Create production-ready scripts with validation and reporting
- β Follow security best practices - never hardcode credentials
- β Optimize for performance using efficient methods
- β Write comment-based help for all functions
- β Use Pester tests to validate functionality
- β Generate HTML reports for better visibility
- β Implement email notifications for critical events
- β Maintain retention policies for backups and logs
π Congratulations!
You've completed the PowerShell for DOS Users eBook! You now have the knowledge to:
- β Transition from DOS/CMD to PowerShell
- β Write production-ready automation scripts
- β Manage Windows systems efficiently
- β Implement best practices and security
- β Create real-world automation solutions
Next Steps:
- Practice with the real-world projects provided
- Explore PowerShell Gallery for community modules
- Join PowerShell communities and forums
- Continue learning advanced topics
- Build your own automation toolkit
π PowerShell Cheat Sheet
Basic Commands & Navigation
# Navigation
Get-Location # Current directory (pwd)
Set-Location C:\Path # Change directory (cd)
Set-Location .. # Go up one level
Push-Location C:\Path # Save and change location
Pop-Location # Return to saved location
# Listing
Get-ChildItem # List items (ls, dir)
Get-ChildItem -Recurse # List recursively
Get-ChildItem -Force # Include hidden files
Get-ChildItem *.txt # Filter by extension
# Create
New-Item -Path "file.txt" -ItemType File
New-Item -Path "folder" -ItemType Directory
New-Item -Path "file.txt" -ItemType File -Value "content"
# Copy
Copy-Item -Path "source.txt" -Destination "dest.txt"
Copy-Item -Path "folder" -Destination "dest" -Recurse
# Move
Move-Item -Path "source.txt" -Destination "dest.txt"
# Delete
Remove-Item -Path "file.txt"
Remove-Item -Path "folder" -Recurse -Force
# Rename
Rename-Item -Path "old.txt" -NewName "new.txt"
# Test existence
Test-Path "C:\file.txt"
Variables & Data Types
# Declaration
$variable = "value"
$number = 42
$array = @(1, 2, 3, 4, 5)
$hashtable = @{Key1 = "Value1"; Key2 = "Value2"}
# Special Variables
$PSVersionTable # PowerShell version
$env:USERNAME # Environment variables
$env:COMPUTERNAME
$env:PATH
$_ # Current pipeline object
$args # Script arguments
$PSScriptRoot # Script directory
$PWD # Current directory
# Type Casting
[int]$number = "42"
[string]$text = 123
[datetime]$date = "2024-01-01"
[array]$list = 1,2,3
# Arrays
$array = @(1, 2, 3, 4, 5)
$array[0] # First element
$array[-1] # Last element
$array.Count # Number of elements
$array += 6 # Add element
$array -contains 3 # Check if contains
# Hashtables
$hash = @{
Name = "John"
Age = 30
City = "New York"
}
$hash["Name"] # Access value
$hash.Name # Dot notation
$hash.Keys # All keys
$hash.Values # All values
$hash.Add("Email", "[email protected]")
$hash.Remove("Age")
Operators
# Comparison (Case-Insensitive)
-eq # Equal
-ne # Not equal
-gt # Greater than
-ge # Greater than or equal
-lt # Less than
-le # Less than or equal
-like # Wildcard match
-match # Regex match
-contains # Collection contains
-in # Item in collection
# Case-Sensitive Versions
-ceq, -cne, -cgt, -cge, -clt, -cle, -clike, -cmatch
# Logical Operators
-and # Logical AND
-or # Logical OR
-not # Logical NOT
! # Logical NOT (alternative)
-xor # Exclusive OR
# String Operators
-replace # Replace text
-split # Split string
-join # Join array elements
Control Flow
# If/Else
if ($condition) {
# code
} elseif ($condition2) {
# code
} else {
# code
}
# Switch
switch ($value) {
1 { "One" }
2 { "Two" }
default { "Other" }
}
# Switch with wildcard
switch -Wildcard ($text) {
"*.txt" { "Text file" }
"*.log" { "Log file" }
default { "Unknown" }
}
# ForEach
foreach ($item in $collection) {
Write-Host $item
}
# ForEach-Object (Pipeline)
$collection | ForEach-Object {
Write-Host $_
}
# For
for ($i = 0; $i -lt 10; $i++) {
Write-Host $i
}
# While
while ($condition) {
# code
}
# Do-While
do {
# code
} while ($condition)
# Do-Until
do {
# code
} until ($condition)
# Break & Continue
break # Exit loop
continue # Skip to next iteration
Functions
# Basic Function
function Get-Something {
param($Parameter1, $Parameter2)
# code
return $result
}
# Advanced Function
function Get-Something {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Name,
[Parameter(Mandatory=$false)]
[int]$Count = 10,
[ValidateSet("Option1", "Option2")]
[string]$Option
)
begin { }
process { }
end { }
}
# Call Function
Get-Something -Name "Test" -Count 5
Pipeline & Filtering
# Where-Object (Filtering)
Get-Process | Where-Object {$_.CPU -gt 100}
Get-Service | Where-Object {$_.Status -eq "Running"}
Get-ChildItem | Where-Object {$_.Length -gt 1MB}
# Select-Object (Selecting Properties)
Get-Process | Select-Object Name, CPU, Memory
Get-Process | Select-Object -First 10
Get-Process | Select-Object -Last 5
Get-Process | Select-Object -Unique
# Sort-Object
Get-Process | Sort-Object CPU -Descending
Get-ChildItem | Sort-Object Length
# Measure-Object
Get-ChildItem | Measure-Object -Property Length -Sum -Average
Get-Process | Measure-Object -Property CPU -Maximum -Minimum
# Group-Object
Get-Service | Group-Object Status
Get-Process | Group-Object Company
Output & Formatting
# Console Output
Write-Host "Message"
Write-Host "Message" -ForegroundColor Green
Write-Output "Data"
Write-Verbose "Verbose message" -Verbose
Write-Warning "Warning message"
Write-Error "Error message"
# Formatting
Format-Table # Table format
Format-List # List format
Format-Wide # Wide format
Out-GridView # GUI grid view
# Export
Export-Csv -Path "file.csv" -NoTypeInformation
Export-Clixml -Path "file.xml"
ConvertTo-Json | Out-File "file.json"
ConvertTo-Html | Out-File "file.html"
# Import
Import-Csv -Path "file.csv"
Import-Clixml -Path "file.xml"
Get-Content "file.json" | ConvertFrom-Json
Error Handling
# Try/Catch
try {
# code that might fail
Get-Item "C:\nonexistent.txt" -ErrorAction Stop
}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
finally {
# cleanup code
}
# Error Action Preference
-ErrorAction Stop # Stop on error
-ErrorAction Continue # Continue on error (default)
-ErrorAction SilentlyContinue # Suppress errors
-ErrorAction Inquire # Ask user
# Throw custom error
throw "Custom error message"
# Test error
if (-not (Test-Path $path)) {
throw "Path not found: $path"
}
Registry Operations
# Read Registry
Get-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Setting"
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
# Create Key
New-Item -Path "HKCU:\Software\MyApp" -Force
# Create Value
New-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Setting" -Value "Value" -PropertyType String
# Modify Value
Set-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Setting" -Value "NewValue"
# Delete Value
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Setting"
# Delete Key
Remove-Item -Path "HKCU:\Software\MyApp" -Recurse
# Test Existence
Test-Path "HKCU:\Software\MyApp"
Services & Processes
# Services
Get-Service # List all services
Get-Service -Name "Spooler" # Get specific service
Get-Service | Where-Object {$_.Status -eq "Running"}
Start-Service -Name "Spooler"
Stop-Service -Name "Spooler"
Restart-Service -Name "Spooler"
Set-Service -Name "Spooler" -StartupType Automatic
# Processes
Get-Process # List all processes
Get-Process -Name "notepad" # Get specific process
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
Start-Process "notepad.exe"
Start-Process "notepad.exe" -ArgumentList "file.txt"
Stop-Process -Name "notepad"
Stop-Process -Id 1234
WMI & CIM
# System Information
Get-CimInstance -ClassName Win32_OperatingSystem
Get-CimInstance -ClassName Win32_ComputerSystem
Get-CimInstance -ClassName Win32_BIOS
Get-CimInstance -ClassName Win32_Processor
# Disk Information
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3"
# Services & Processes
Get-CimInstance -ClassName Win32_Service -Filter "State = 'Running'"
Get-CimInstance -ClassName Win32_Process
# Network
Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = True"
# CIM Sessions
$session = New-CimSession -ComputerName "Server01"
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session
Remove-CimSession -CimSession $session
Remote Management
# Enable Remoting
Enable-PSRemoting -Force
# Interactive Session
Enter-PSSession -ComputerName "Server01"
Exit-PSSession
# Run Command on Remote Computer
Invoke-Command -ComputerName "Server01" -ScriptBlock {
Get-Service
}
# Run on Multiple Computers
Invoke-Command -ComputerName "Server01", "Server02" -ScriptBlock {
Get-Process
}
# Persistent Sessions
$session = New-PSSession -ComputerName "Server01"
Invoke-Command -Session $session -ScriptBlock { Get-Service }
Remove-PSSession -Session $session
# Copy Files
Copy-Item -Path "local.txt" -Destination "C:\remote.txt" -ToSession $session
Modules
# List Modules
Get-Module # Loaded modules
Get-Module -ListAvailable # Available modules
# Import Module
Import-Module ModuleName
# Remove Module
Remove-Module ModuleName
# Find Module in Gallery
Find-Module -Name "ModuleName"
# Install from Gallery
Install-Module -Name "ModuleName" -Scope CurrentUser
# Update Module
Update-Module -Name "ModuleName"
# Uninstall Module
Uninstall-Module -Name "ModuleName"
# Module Commands
Get-Command -Module ModuleName
Help System
# Update Help
Update-Help -Force
# Get Help
Get-Help Get-Process
Get-Help Get-Process -Full
Get-Help Get-Process -Examples
Get-Help Get-Process -Online
Get-Help Get-Process -Parameter Name
# Find Commands
Get-Command # All commands
Get-Command *process* # Search commands
Get-Command -Verb Get # Commands by verb
Get-Command -Noun Service # Commands by noun
Get-Command -Module ModuleName # Commands in module
# Get Aliases
Get-Alias
Get-Alias -Name ls
Get-Alias -Definition Get-ChildItem
Common Patterns
# Get top 10 processes by CPU
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
# Get disk space
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID, @{Name="SizeGB";Expression={[math]::Round($_.Size/1GB,2)}},
@{Name="FreeGB";Expression={[math]::Round($_.FreeSpace/1GB,2)}}
# Get running services
Get-Service | Where-Object {$_.Status -eq "Running"} | Sort-Object Name
# Find large files
Get-ChildItem -Recurse | Where-Object {$_.Length -gt 100MB} |
Sort-Object Length -Descending
# Get installed software
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher
# Test network connectivity
Test-Connection -ComputerName "google.com" -Count 4
# Get event logs
Get-EventLog -LogName System -Newest 10
Get-WinEvent -LogName System -MaxEvents 10
# Measure command execution time
Measure-Command { Get-Process }
# Get Windows version
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, BuildNumber
Regular Expressions
# Match Operators
"text" -match "pattern" # Returns $true/$false
"text" -replace "old", "new" # Replace text
# Common Patterns
^ # Start of string
$ # End of string
. # Any character
* # Zero or more
+ # One or more
? # Zero or one
\d # Digit [0-9]
\w # Word character [a-zA-Z0-9_]
\s # Whitespace
[abc] # Character class
[^abc] # Negated class
(pattern) # Capture group
# Examples
$email -match "^[\w.-]+@[\w.-]+\.\w+$"
$ip -match "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
$phone -match "^\d{3}-\d{3}-\d{4}$"
$text -replace "\s+", " " # Replace multiple spaces with one
Keyboard Shortcuts & Tips
PowerShell Console Shortcuts:
| Shortcut | Action |
|---|---|
| Tab | Auto-complete command/parameter/path |
| Ctrl + Space | Show all possible completions |
| β / β | Navigate command history |
| F7 | Show command history dialog |
| Ctrl + C | Cancel current command |
| Ctrl + L | Clear screen (same as Clear-Host) |
| F8 | Search command history |
| Ctrl + R | Reverse search history |
| Home / End | Move to start/end of line |
| Ctrl + β / β | Move by word |
Quick Tips:
- β
Use
Get-Command *keyword*to find commands - β
Use
Get-Help CommandName -Examplesfor quick examples - β Press Tab to auto-complete everything
- β
Use
-WhatIfto preview changes without executing - β
Use
-Confirmto prompt before executing - β
Use
Get-Memberto explore object properties and methods - β
Use
Measure-Commandto measure script performance - β
Use
$PSVersionTableto check PowerShell version - β
Use
Get-Historyto see command history - β
Use
clsorClear-Hostto clear the console
DOS to PowerShell Translation
Common DOS Commands in PowerShell:
| DOS Command | PowerShell Equivalent | Description |
|---|---|---|
| dir | Get-ChildItem (ls, dir) | List directory contents |
| cd | Set-Location (cd) | Change directory |
| copy | Copy-Item (copy, cp) | Copy files |
| move | Move-Item (move, mv) | Move files |
| del / erase | Remove-Item (del, rm) | Delete files |
| md / mkdir | New-Item -ItemType Directory | Create directory |
| ren | Rename-Item (ren) | Rename file |
| type | Get-Content (cat, type) | Display file contents |
| cls | Clear-Host (cls, clear) | Clear screen |
| echo | Write-Output (echo) | Display text |
| set | $env:VAR = "value" | Set environment variable |
| ipconfig | Get-NetIPConfiguration | Network configuration |
| tasklist | Get-Process | List processes |
| taskkill | Stop-Process | Kill process |
π― Remember:
- β PowerShell is object-based, not text-based
- β Use the pipeline to chain commands together
- β Tab completion is your best friend
- β Use Get-Help and Get-Command to explore
- β -WhatIf is your safety net for testing
- β PowerShell is case-insensitive (except for some operators)
- β Use ISE or VS Code for script development
- β Always test scripts in a non-production environment first