πŸ“˜ 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

  1. Week 1-2: Chapters 1-5 (Foundations) - Understand basics and setup
  2. Week 3-4: Chapters 6-10 (Core Skills) - Master programming fundamentals
  3. Week 5-8: Chapters 11-18 (Practical) - Apply skills to real scenarios
  4. Daily Practice: Complete all exercises before moving forward
  5. Weekly Review: Revisit previous chapters and practice
  6. Estimated Time: 8-10 weeks (1-2 hours daily)

⚑ For CMD/DOS Users (Fast Track)

  1. Day 1-2: Chapters 1-3 - Understand PowerShell philosophy and differences
  2. Day 3: Chapter 4 - DOS to PowerShell translation (your quick reference)
  3. Day 4-5: Chapters 6-8 - Variables, operators, control flow
  4. Week 2-3: Chapters 11-15 - Jump to practical system administration
  5. Week 4: Chapters 16-18 - Advanced topics and best practices
  6. Reference: Use Cheat Sheet frequently
  7. Estimated Time: 4-5 weeks

πŸ“– For Quick Reference Users

  1. Start Here: PowerShell Cheat Sheet (comprehensive quick reference)
  2. Topic-Based: Use sidebar to find specific topics you need
  3. Code Examples: Review and copy examples from relevant chapters
  4. 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
Common DOS Commands
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

PowerShell
Get-Date

Output:

Friday, June 5, 2026 2:38:15 PM

Example 2: Get System Information

PowerShell
Get-ComputerInfo | Select-Object CsName, WindowsVersion, OsArchitecture

Example 3: List Running Processes

PowerShell
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.

πŸ’‘ Did You Know?
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:

Command Prompt
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:

PowerShell
# 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 Aliases:
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 ipconfig or ping
  • 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:

Command Prompt
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:

PowerShell
# 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:

PowerShell - Discoverability
# 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:

PowerShell - Pipeline Examples
# 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:

PowerShell - Remote Management
# 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:

Command Prompt
forfiles /p "C:\Temp" /s /m *.* /d -30 /c "cmd /c del @path"

PowerShell:

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:

Command Prompt
REM Very limited - can only output text
sc query state= all > services.txt

PowerShell:

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:

Command Prompt
REM Text output, hard to parse
wmic logicaldisk get caption,freespace,size

PowerShell:

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)

πŸ“‹ Migration Checklist:
  1. Week 1-2: Use PowerShell for simple commands (dir, cd, copy)
  2. Week 3-4: Learn Get-Command, Get-Help, Get-Member
  3. Month 2: Replace one batch file with PowerShell script
  4. Month 3: Learn pipeline and object manipulation
  5. Month 4+: Tackle advanced scenarios (remoting, automation)

2.7.2 Common Pitfalls to Avoid

⚠️ Common Mistakes:

  • Using CMD syntax in PowerShell: dir /s won't workβ€”use Get-ChildItem -Recurse
  • Ignoring execution policy: Scripts won't run without proper policy
  • Not using proper error handling: Always use -ErrorAction parameter
  • 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 Solution
# PowerShell equivalent
Get-ChildItem -Recurse -Filter *.txt | Select-Object -ExpandProperty FullName

# Or shorter:
Get-ChildItem -Recurse -Filter *.txt | % FullName

Explanation:

  • /s becomes -Recurse
  • /b (bare format) becomes Select-Object -ExpandProperty FullName
  • *.txt becomes -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
PowerShell 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
PowerShell 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
PowerShell 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
PowerShell 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
πŸ’‘ Bottom Line:
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.

πŸ’‘ Key Insight: PowerShell doesn't just replace DOS commandsβ€”it enhances them with object-oriented capabilities. Every DOS command you know has a more powerful PowerShell equivalent.

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:

PowerShell - Understanding Aliases
# 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
⚠️ Important: While aliases work in interactive sessions, always use full cmdlet names in scripts for clarity and compatibility.

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

Command Prompt
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

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)

Command Prompt
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)

PowerShell
# 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

Command Prompt
REM List processes
tasklist

REM Find specific process
tasklist | find "chrome"

REM Kill a process
taskkill /IM chrome.exe /F

PowerShell

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

Command Prompt
REM Ping a host
ping google.com

REM Ping with count
ping -n 4 google.com

REM Get IP config
ipconfig /all

PowerShell

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

Command Prompt
REM Find files by name
dir /s /b *.txt

REM Search for text in files
findstr /s /i "error" *.log

PowerShell

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

PowerShell - 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

PowerShell - Pipeline Examples
# 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

PowerShell - Remote Management
# 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

PowerShell - 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

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

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

PowerShell
# ❌ 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

PowerShell
# ❌ 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

PowerShell
# ❌ 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 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 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:

  1. Lists all .log files in C:\Windows\Logs
  2. Finds files larger than 1MB
  3. Copies them to C:\Backup
Click to see solution
PowerShell 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
PowerShell 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 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-Alias to 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
πŸ’‘ Important: Throughout this book, we'll use PowerShell 7.5+ (the latest cross-platform version). Most commands work in PowerShell 5.1, but we'll highlight any differences.

4.2 Installing PowerShell 7+

4.2.1 Installation on Windows

Method 1: Using Windows Package Manager (Recommended)

PowerShell - Install via winget
# Install using winget (Windows 11/10)
winget install Microsoft.PowerShell

# Verify installation
pwsh --version

Method 2: Using MSI Installer

  1. Visit: https://github.com/PowerShell/PowerShell/releases
  2. Download the latest .msi file for Windows
  3. Run the installer with default options
  4. Restart your terminal

Method 3: Using Chocolatey

PowerShell - Install via Chocolatey
# Install using Chocolatey
choco install powershell-core -y

# Verify installation
pwsh --version

4.2.2 Installation on Linux

Ubuntu/Debian:

Bash - Install on 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:

Bash - Install on 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):

Bash - Install on macOS
# 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

PowerShell - Verify 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

PowerShell - Execution Policy Management
# 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
⚠️ Security Warning: Never set execution policy to 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

PowerShell - Create 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 - Sample Profile Configuration
# ============================================
# 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

PowerShell - Reload Profile
# 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.

PowerShell - Module Discovery
# 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

PowerShell - Install 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

PowerShell - Module Management
# 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:

PowerShell - Install VS Code (Windows)
winget install Microsoft.VisualStudioCode

Linux:

Bash - Install VS Code (Linux)
sudo snap install code --classic

macOS:

Bash - Install VS Code (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:

JSON - VS Code Settings
{
    // 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

PowerShell - Install 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:

JSON - Windows Terminal Settings
{
    "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:

  1. Choose the appropriate installation method for your OS
  2. Install PowerShell 7+
  3. Open PowerShell and run $PSVersionTable
  4. Verify PSVersion is 7.0 or higher
Click to see solution
PowerShell 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
PowerShell 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
PowerShell 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
PowerShell 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
PowerShell 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 RemoteSigned provides 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 Convention: PowerShell cmdlets follow a consistent Verb-Noun naming pattern:
  • Verb: What action to perform (Get, Set, New, Remove, etc.)
  • Noun: What to act upon (Process, Service, Item, etc.)
Example: 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

PowerShell - 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

PowerShell - 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

PowerShell - 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

PowerShell - Listing Files
# 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

PowerShell - File Operations
# 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

PowerShell - File Content Operations
# 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

PowerShell - Path Testing
# 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

PowerShell - 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

PowerShell - Process Control
# 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

PowerShell - Process Monitoring
# 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

PowerShell - 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

PowerShell - Service Control
# 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
⚠️ Warning: Stopping critical services can cause system instability. Always verify the service purpose before stopping it. Use Get-Service -Name "ServiceName" | Format-List * to see dependencies.

5.5.3 Service Monitoring Script

PowerShell - Service Monitoring
# 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

PowerShell - 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

PowerShell - 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

PowerShell - System Report
# 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

PowerShell - 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

PowerShell - 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

PowerShell - 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

PowerShell - Network Monitoring
# 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

PowerShell - 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

PowerShell - File 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

PowerShell - Administrator Check
# 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

PowerShell - Date and Time Operations
# 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

PowerShell - 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

PowerShell - 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

PowerShell - Event Log Analysis
# 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
PowerShell Solution - Exercise 1
# 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
PowerShell Solution - Exercise 2
# 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
PowerShell Solution - Exercise 3
# 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
PowerShell Solution - Exercise 4
# 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
PowerShell Solution - Exercise 5
# 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.

πŸ’‘ Key Concept: PowerShell is dynamically typed, meaning you don't need to declare a variable's type explicitly. PowerShell automatically determines the type based on the value you assign.

6.1.1 Variable Naming Rules

  • Must start with $
  • Can contain letters, numbers, and underscores
  • Are not case-sensitive ($name and $Name are the same)
  • Should be descriptive and follow naming conventions
Variable Naming Examples
# 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

Basic Variable Assignment
# 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

Getting Variable Type Information
# 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

Single vs Double 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

Common 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

Numeric 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 Values and Operations
# 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

Array Basics
# 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

Working with Arrays
# 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

Hashtable Basics
# 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

Iterating 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.

Type Casting Examples
# 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
Variable Scope Examples
# 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
Using Automatic Variables
# $_ - 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: $userName instead of $u
  • Use camelCase or PascalCase: $serverName or $ServerName
  • Declare types when needed: [int]$count = 0 for 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 = $null after 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.

  1. Create variables for: firstName, lastName, age, email, isActive
  2. Combine firstName and lastName into fullName
  3. Calculate birth year from age
  4. Create a hashtable with all user information
  5. Display the information in a formatted way
Show Solution
Exercise 1 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.

  1. Create an array of 10 random numbers between 1 and 100
  2. Find the sum, average, minimum, and maximum
  3. Filter numbers greater than 50
  4. Sort the array in descending order
  5. Display the results
Show Solution
Exercise 2 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.

  1. Given a log entry: "2026-06-05 14:30:15 ERROR UserService Failed to authenticate user: [email protected]"
  2. Extract: date, time, level, service, message, email
  3. Store in a hashtable
  4. Display in a formatted way
Show Solution
Exercise 3 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.

πŸ’‘ Key Concept: Unlike many programming languages that use symbols like == 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

Basic Arithmetic Operations
# 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

Advanced Arithmetic
# 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
Assignment Operators in Action
# 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
Comparison Examples
# 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
Logical Operators Examples
# 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)

Using -like with Wildcards
# * 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)

Using -match with Regex
# 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 and Split Operations
# 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]
Type Checking and Conversion
# 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 and In Operators
# -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
Operator Precedence Examples
# 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"
}
⚠️ Best Practice: Always use parentheses to make your intentions clear, even when not strictly necessary. This improves code readability and prevents errors.

7.10 Special Operators

7.10.1 Range Operator (..)

Range Operator Examples
# 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 (&)

Call Operator Examples
# 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 (,)

Comma Operator for Arrays
# 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
Calculator Script
# 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
# 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 Script
# 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.

Note: PowerShell's control flow syntax is similar to C# and other C-style languages, making it familiar to many programmers.

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

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

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

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

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 Logical Operators
# 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

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

Switch with Multiple Conditions
$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

Switch with Wildcards
$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

Switch with Regular Expressions
$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

Processing Arrays with Switch
$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

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

For Loop with Custom Step
# 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

Nested For Loops - Multiplication Table
# 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

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

Iterating Through 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

ForEach-Object in Pipeline
# 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

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

Interactive While Loop
$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

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

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 vs Do-While Comparison
# 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

Break Statement
# 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

Continue Statement
# 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

Breaking Nested Loops 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 foreach for collections
    • Use for when you need an index
    • Use while when iterations are unknown
  • Avoid infinite loops: Always ensure loop conditions can become false
  • Use meaningful variable names: $userAge instead 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

Process Files by Extension
$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

Monitor and Restart Services
$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

Validate User Input with Retry Logic
$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
Grade Calculator 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
Number Guessing Game 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
Disk Space Monitor 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?

Quiz Question 1
$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?

Quiz Question 4
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?

Quiz Question 6
$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
Basic Function Syntax
function FunctionName {
    # Function body
    # Code to execute
}

9.2 Creating Basic Functions

Let's start with simple functions:

Simple Function Example
# Define a simple function
function Say-Hello {
    Write-Host "Hello, PowerShell!"
}

# Call the function
Say-Hello

# Output: Hello, PowerShell!
Function with Logic
# 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 Single Parameter
# 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 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
Parameters with Default Values
# 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:

Using Return Keyword
# 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
Implicit Return (Output)
# 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
Returning Multiple Values
# 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

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

Parameter Validation Attributes
# 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
More Validation Attributes
# 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

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:

Local vs Script Scope
# 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
Using Scope Modifiers
# 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

Pipeline Input
# 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
Begin, Process, End Blocks
# 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 with Help Documentation
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

Basic Script Structure (Save as MyScript.ps1)
# 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

Ways to Run 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

Managing 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

Complete 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

Log File Analysis Function
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

Automated 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
πŸ’‘ Best Practice: Always implement error handling in production scripts. Assume that anything that can go wrong will go wrong, and plan accordingly.

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.

Terminating Error Example
# 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.

Non-Terminating Error Example
# 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

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

Try/Catch/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

Specific Exception Handling
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)
Using Error Variables
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
ErrorAction Examples
# 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:

Throw Examples
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:

Write-Error Examples
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

🎯 Error Handling Best Practices:
  1. Always use -ErrorAction Stop with try/catch - Ensures errors are caught
  2. Catch specific exceptions first - Handle known errors specifically
  3. Always include a generic catch - Handle unexpected errors
  4. Use Finally for cleanup - Close files, connections, etc.
  5. Provide meaningful error messages - Help users understand what went wrong
  6. Log errors appropriately - Record errors for troubleshooting
  7. Don't catch errors you can't handle - Let them propagate up
  8. Test error paths - Ensure error handling works as expected
  9. Use Write-Error for warnings - Use throw for critical errors
  10. Include context in errors - Show what operation failed

10.8 Debugging Techniques

10.8.1 Write-Debug and Write-Verbose

Debug and Verbose Output
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

Using Breakpoints
# 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

Recording Script Output
# 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

Robust File Processing
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

Retry Logic Example
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

Error Logging Function
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
Exercise 1 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
Exercise 2 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
Exercise 3 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

🎯 Key Takeaways:
  • 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.

Key Cmdlets Covered:
  • Get-ChildItem - List files and folders
  • New-Item - Create files and folders
  • Copy-Item - Copy files and folders
  • Move-Item - Move/rename files and folders
  • Remove-Item - Delete files and folders
  • Test-Path - Check if path exists
  • Get-Content - Read file content
  • Set-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

Basic Directory 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

Filter by Extension and Pattern
# 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 Subdirectories
# 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 Directories
# 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 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 Files
# 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 Directories
# 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 Operations
# 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 Operations
# 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 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 Directories
# 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 File Content
# 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 File Content
# 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)

Path Testing
# 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

Path Operations
# 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 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

Log Cleanup Script
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

Key Takeaways:
  • 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

Note: The Windows Registry is a critical system database. Always backup before making changes, and test in a non-production environment first.

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:

View Registry Drives
# List registry drives
Get-PSDrive -PSProvider Registry

# Output shows:
# HKCU: (HKEY_CURRENT_USER)
# HKLM: (HKEY_LOCAL_MACHINE)
Navigate Registry Like File System
# Change to registry location
Set-Location HKCU:\Software

# List keys (like folders)
Get-ChildItem

# View current location
Get-Location

12.3 Reading Registry Values

Get-ItemProperty - Read 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"
Practical Example: Get Windows Version
# 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

New-Item - Create Registry Key
# 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
New-ItemProperty - Create Registry Values
# 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

Set-ItemProperty - Modify Existing 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-ItemProperty - Rename Registry Value
# Rename a registry value
Rename-ItemProperty -Path "HKCU:\Software\MyApp" -Name "AppPath" -NewName "ApplicationPath"

12.6 Deleting Registry Keys and Values

⚠️ Warning: Deleting registry keys can break applications or Windows. Always backup and test first!
Remove-ItemProperty - Delete Registry Value
# Delete a specific value
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version"

# Delete with confirmation
Remove-ItemProperty -Path "HKCU:\Software\MyApp" -Name "Version" -Confirm
Remove-Item - Delete Registry Key
# 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

Test-Path - Check if Key Exists
# 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 (Backup)
# 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 Registry Key (Restore)
# 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

Example 1: Check if Software is Installed
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"
Example 2: Configure Application Settings
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
Example 3: Audit Registry Changes
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:

  1. Always Backup: Export keys before making changes
  2. Test First: Use -WhatIf parameter when available
  3. Use Try/Catch: Wrap registry operations in error handling
  4. Check Existence: Use Test-Path before accessing keys
  5. Use Full Paths: Always specify complete registry paths
  6. Avoid HKLM Changes: Modify HKCU when possible (no admin required)
  7. Document Changes: Keep a log of what you modify
  8. Use Correct Types: Specify proper PropertyType for values
  9. Handle Errors: Use -ErrorAction SilentlyContinue with proper checks
  10. Restart if Needed: Some registry changes require logout/restart
Safe Registry Modification Template
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

Note: Managing services and processes requires appropriate permissions. Some operations require administrator rights. Always test in a non-production environment first.

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-Service - List Services
# 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"
Filter Services by Status
# 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 Detailed Service Information
# 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

⚠️ Warning: Stopping critical services can affect system stability. Always verify dependencies before stopping services.
Start-Service - Start a Service
# 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-Service - Stop a Service
# 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-Service - Restart a Service
# 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

Set-Service - Configure Service Properties
# 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
Service Startup Types
# 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

Check 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-Process - List Processes
# 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*"
Filter and Sort Processes
# 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 Detailed Process Information
# 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-Process - Start a New Process
# Start notepad
Start-Process notepad

# Start process with arguments
Start-Process notepad -ArgumentList "C:	empile.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 with Redirection
# 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

⚠️ Warning: Stopping processes forcefully can cause data loss. Save your work before terminating processes.
Stop-Process - Terminate a Process
# 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
Gracefully Stop Process
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

Monitor Process Resource Usage
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

Example 1: Service Health Check and Auto-Restart
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
Example 2: Process Resource Monitor with Alerts
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
Example 3: Generate Service Status Report
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)

"@ foreach ($service in $services) { $wmiService = Get-WmiObject Win32_Service -Filter "Name='$($service.Name)'" $statusClass = if ($service.Status -eq "Running") { "running" } else { "stopped" } $html += @" "@ } $html += @"
Display Name Name Status Startup Type
$($service.DisplayName) $($service.Name) $($service.Status) $($wmiService.StartMode)
"@ # 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
Example 4: Kill Unresponsive Processes
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

Complex Service 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
Complex Process Queries
# 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:

  1. Check Dependencies: Always verify service dependencies before stopping
  2. Use -WhatIf: Test commands before executing them
  3. Error Handling: Use try/catch blocks for service operations
  4. Logging: Keep logs of service changes
  5. Startup Type: Be cautious when changing service startup types
  6. Test in Non-Production: Test service changes in dev environment first
  7. Document Changes: Keep records of why services were modified
  8. Use Credentials Carefully: When changing service accounts, ensure proper permissions

βœ… Process Management Best Practices:

  1. Graceful Shutdown: Try CloseMainWindow() before Stop-Process -Force
  2. Check Responding: Verify if process is responding before terminating
  3. Save Work: Warn users before killing processes
  4. Monitor Resources: Track CPU and memory before taking action
  5. Use Timeouts: Set reasonable timeouts for process operations
  6. Handle Errors: Use -ErrorAction and try/catch
  7. Avoid System Processes: Never kill critical system processes
  8. Log Actions: Keep audit trail of process terminations
Safe Service Management Template
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

Real-Time Performance Dashboard
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

Manage Services on Remote Computers
# 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")
Manage Processes on Remote Computers
# 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

Note: CIM (Common Information Model) cmdlets are the modern replacement for WMI cmdlets. Use CIM cmdlets for new scripts as they are more efficient and support PowerShell remoting better.

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-CimInstance - Basic System Info
# 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
Filtering CIM Queries
# 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}
Selecting Specific Properties
# 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
Hardware Information Examples
# 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

Creating and Using 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
Query Multiple Computers
# 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

Discover Available 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"]
Invoke-CimMethod - Execute Methods
# 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
⚠️ Warning: Invoking CIM methods can make system changes. Always test in a non-production environment first and ensure you have proper permissions.

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
WMI vs CIM Syntax Comparison
# 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

Example 1: System Inventory Report
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
Example 2: Disk Space Monitoring
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
Example 3: Network Adapter Configuration
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
Example 4: Service Dependency Checker
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:

  1. Use CIM cmdlets: Always prefer CIM over WMI for new scripts
  2. Use CIM Sessions: Create sessions for multiple queries to same computer
  3. Server-side filtering: Use -Filter parameter instead of Where-Object when possible
  4. Select properties: Only retrieve properties you need for better performance
  5. Close sessions: Always clean up CIM sessions with Remove-CimSession
  6. Error handling: Use try/catch blocks for CIM operations
  7. Avoid Win32_Product: This class is very slow and can trigger repairs
  8. Use -ErrorAction: Control error behavior appropriately
  9. Test queries: Test WMI/CIM queries on test systems first
  10. Document classes: Comment which CIM classes you're using and why
Performance Comparison Example
# 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

Note: PowerShell Remoting uses WS-Management protocol (WinRM) and requires proper configuration. Always ensure you have appropriate permissions and network access before attempting remote management.

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-PSRemoting - Setup 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 WinRM Configuration
# 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
Configure Trusted Hosts (Workgroup Environment)
# 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"
⚠️ Security Warning: Adding computers to TrustedHosts bypasses mutual authentication. Only use in trusted networks or workgroup environments. Domain-joined computers use Kerberos and don't need TrustedHosts configuration.

15.3 Interactive Remote Sessions (One-to-One)

Enter-PSSession - Interactive Remote Session
# 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-PSSession or exit to return to local session
  • Session remains active until explicitly closed

15.4 Running Commands on Multiple Computers (One-to-Many)

Invoke-Command - Run Commands Remotely
# 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"
Passing Arguments to Remote Commands
# 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
}
Processing Remote Results
# 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

New-PSSession - Create 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"
Using Persistent Sessions
# 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

Background Jobs with Remoting
# 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"
Parallel Execution with Throttling
# 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
Disconnected Sessions
# 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

Example 1: Multi-Server Health Check
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"
Example 2: Remote Software Installation
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
Example 3: Remote Log Collection
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
Example 4: Remote Configuration Management
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
Diagnostic Commands
# 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:

  1. Use CIM sessions: Prefer CIM cmdlets over WMI for better performance
  2. Persistent sessions: Use New-PSSession for multiple operations
  3. Close sessions: Always clean up with Remove-PSSession
  4. Error handling: Use try/catch blocks for remote operations
  5. Throttle limits: Set appropriate -ThrottleLimit for large deployments
  6. Credentials: Store credentials securely, avoid plaintext
  7. TrustedHosts: Use sparingly, prefer domain authentication
  8. HTTPS: Use HTTPS (port 5986) for sensitive operations
  9. Logging: Log remote operations for audit trail
  10. Test first: Always test on non-production systems
Secure Credential Handling
# 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

Note: PowerShell modules are the recommended way to package and distribute reusable code. They provide better organization, discoverability, and version management than standalone scripts.

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

Get-Module - List 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-Module - Load Modules
# 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-Module - Unload Modules
# 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 and Modify Module Paths
# 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

Step 1: Create Module File (.psm1)
# 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
Step 2: Test the Module
# 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

New-ModuleManifest - Create Manifest File
# 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-ModuleManifest - Validate Manifest
# 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

Module with Private and Public Functions
# 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 Classes (PowerShell 5.0+)
# 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

Script Template with 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"
}
Script Signing and Execution Policy
# 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

Find-Module - Search PowerShell Gallery
# 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 - Install from Gallery
# 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-Module - Update Installed Modules
# 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-Module - Remove Modules
# 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

Example 1: System Administration Module
# 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
Example 2: User Management Module
# 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:

  1. Use Approved Verbs: Get-Verb to see approved verbs for function names
  2. Create Manifests: Always create .psd1 manifest files for versioning
  3. Comment-Based Help: Include comprehensive help for all functions
  4. Export Explicitly: Use Export-ModuleMember to control what's public
  5. Version Control: Use semantic versioning (Major.Minor.Patch)
  6. Error Handling: Implement proper try/catch blocks
  7. Parameter Validation: Use ValidateSet, ValidateRange, etc.
  8. Support -WhatIf: Add [CmdletBinding(SupportsShouldProcess)] for changes
  9. Test Thoroughly: Use Pester for unit testing
  10. Document Dependencies: List required modules in manifest
Module Structure Best Practice
# 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:

  1. Use Comment-Based Help: Document synopsis, description, parameters, examples
  2. Parameter Validation: Validate input before processing
  3. Error Handling: Use try/catch and $ErrorActionPreference
  4. Logging: Implement logging for troubleshooting
  5. Use $PSScriptRoot: Reference files relative to script location
  6. Version Your Scripts: Include version number in header
  7. Test Before Deploy: Test in non-production first
  8. Use Source Control: Git for version history
  9. Follow Naming Conventions: Verb-Noun format
  10. 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

Note: This chapter covers advanced PowerShell concepts. Understanding previous chapters is recommended before diving into these 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

Advanced Function Template
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
Dynamic Parameters Example
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)

Basic Regex Patterns
# 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
Real-World Regex Examples
# 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-Job - Background Jobs
# 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
ForEach-Object -Parallel (PowerShell 7+)
# 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
Real-World: Parallel Server Health Check
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)

Basic Class Definition
# 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)"
Advanced Class with Inheritance
# 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)"
    }
}
Real-World: User Management Class
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

Splatting - Cleaner Parameter Passing
# 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-Strings - Multi-line Text
# 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)

ServerStatusUptime
"@
Script Blocks and Invoke-Command
# 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

Performance Comparison Examples
# 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:

  1. Use .NET collections: ArrayList or Generic.List instead of += for arrays
  2. Filter early: Use -Filter parameter instead of Where-Object when possible
  3. Combine filters: Use -and/-or instead of multiple Where-Object
  4. Use .ForEach() method: Faster than ForEach-Object for simple operations
  5. StringBuilder for strings: Use StringBuilder for concatenating many strings
  6. Select properties early: Use Select-Object to reduce data size early
  7. Avoid Get-WmiObject: Use Get-CimInstance (faster)
  8. Use -ReadCount: Process files in batches with Get-Content
  9. Parallel processing: Use ForEach-Object -Parallel for independent operations
  10. Measure performance: Use Measure-Command to test different approaches

17.8 Advanced Error Handling

Custom Error Handling with Trap
# 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

Final Chapter: This chapter brings together everything you've learned with comprehensive best practices and complete real-world automation projects ready for production use.

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
Production-Ready Function Template
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

Advanced Logging Function
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

Configuration File Approach (JSON)
# 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

Comprehensive Error Handling
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

Complete Server Health Monitoring Script
<#
.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 += "" $htmlBody += "" $htmlBody += "" $htmlBody += "" $htmlBody += "" $htmlBody += "
MetricValue
Uptime$($server.Uptime)
CPU Usage$($server.CPUUsage)%
Memory Used$($server.MemoryUsedPercent)%
Recent Errors$($server.RecentErrors)
" if ($server.DiskInfo.Count -gt 0) { $htmlBody += "

Disk Space

" $htmlBody += "" foreach ($disk in $server.DiskInfo) { $htmlBody += "" } $htmlBody += "
DriveSize (GB)Free (GB)Free %
$($disk.Drive)$($disk.SizeGB)$($disk.FreeGB)$($disk.FreePercent)%
" } 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

Complete Backup Automation Script
<#
.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

"@ foreach ($result in $backupResults) { $statusClass = if ($result.Status -eq 'Success') { 'success' } else { 'failed' } $htmlReport += @" "@ } $htmlReport += "
Source Path Backup Name Status Size (MB) Duration
$($result.SourcePath) $($result.BackupName) $($result.Status) $($result.SizeMB) $($result.Duration)
" # 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

Bulk User Creation from CSV
<#
.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:

  1. Never hardcode credentials - Use Get-Credential or secure storage
  2. Use execution policies - Set appropriate policy for your environment
  3. Sign your scripts - Use code signing certificates for production
  4. Validate input - Always validate user input and parameters
  5. Use least privilege - Run with minimum required permissions
  6. Encrypt sensitive data - Use SecureString and encryption
  7. Audit script execution - Enable PowerShell logging
  8. Review third-party scripts - Never run untrusted code
  9. Use constrained language mode - For untrusted environments
  10. Regular security updates - Keep PowerShell and modules updated
Secure Credential Handling
# 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

Pester Testing Example
# 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

Quick Reference Guide: This cheat sheet provides a quick reference to the most commonly used PowerShell commands and syntax patterns covered in this eBook.

Basic Commands & Navigation

File System 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
File & Folder Operations
# 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

Variable Basics
# 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 & Hashtables
# 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 & Logical 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 & Switch
# 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" }
}
Loops
# 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

Function Syntax
# 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

Pipeline Operations
# 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

Output Commands
# 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/Finally
# 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

Registry Commands
# 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

Service Management
# 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

CIM Queries
# 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

PowerShell Remoting
# 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

Module Management
# 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

Getting Help
# 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

Useful One-Liners
# 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

Common Regex Patterns
# 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 -Examples for quick examples
  • βœ… Press Tab to auto-complete everything
  • βœ… Use -WhatIf to preview changes without executing
  • βœ… Use -Confirm to prompt before executing
  • βœ… Use Get-Member to explore object properties and methods
  • βœ… Use Measure-Command to measure script performance
  • βœ… Use $PSVersionTable to check PowerShell version
  • βœ… Use Get-History to see command history
  • βœ… Use cls or Clear-Host to 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