Table of Contents

Many PowerShell scripts starts the same way - a series of Import-Module statements to load dependencies. This pattern is so common that most developers never question it. But this approach has a problem that shows up when scripts fail halfway through execution in production environments.

PowerShell has two ways to manage module dependencies. Most developers use Import-Module, even though the #Requires directive has been around since PowerShell 3.0. The difference between them matters when things go wrong.

Understanding #Requires directive

The #Requires directive is a preprocessor statement that validates script prerequisites before execution begins. Unlike runtime imports, it prevents script execution entirely if requirements are not met.

#Requires -Version 7.0
#Requires -Modules Az.Accounts, Az.Storage, SqlServer
#Requires -PSEdition Core

# Script code starts here
Write-Host "All requirements met, executing..."

When PowerShell encounters these directives, it validates all conditions before parsing the rest of the script. If any requirement fails, the script terminates with a clear error message indicating which prerequisite is missing:

The script 'test.ps1' cannot be run because the following modules that are specified by 
the "#requires" statements of the script are missing: The module 'SqlServer' cannot be found.

While #Requires statements can appear anywhere in your script, their scope is always global. PowerShell processes all #Requires statements before executing any code, regardless of their position:

function Test-Database {
    #Requires -Modules SqlServer  # Still applies globally
    Invoke-SqlCmd -Query "SELECT 1"
}

Write-Host "Starting script..."  # Won't execute if SqlServer is missing

#Requires -Version 7.0  # Can appear after code - still validated first

This behavior differs from typical script execution flow. Even when placed inside functions, conditional blocks, or at the end of a script, #Requires statements affect the entire script execution.

The position in the script does not affect the sequence of validation - all requirements must be satisfied before any code runs.

Are you interested in staying up-to-date with the latest developments in #Azure, #CloudComputing, #PlatformEngineering, #DevOps, #AI?

Follow me on LinkedIn for regular updates and insights on these topics. I bring a unique perspective and in-depth knowledge to the table. Don't miss out on the valuable content I share – give me a follow today!

#Requires vs Import-Module: Key differences

Execution timing

The fundamental difference lies in when validation occurs:

# With #Requires - fails before any code runs
#Requires -Modules SqlServer
$data = Get-ImportantData  # This never executes if SqlServer is missing
Backup-SqlDatabase

# With Import-Module - fails during execution
$data = Get-ImportantData  # This executes
Import-Module SqlServer     # Failure here - after data retrieval
Backup-SqlDatabase

Why Import-Module at script start is not enough

A common misconception is that placing Import-Module statements at the beginning of a script provides the same guarantees as #Requires. However, this is not always the case:

# This pattern seems safe but has hidden issues
param(
    [Parameter(Mandatory)]
    [string]$DatabaseServer
)

Import-Module SqlServer  # Line 5 - seems early enough?

# Problem: User is prompted for parameter even if SqlServer module is missing

The execution order reveals the issue:

  1. PowerShell processes the param block first
  2. Validates parameters and prompts for missing values
  3. Only then attempts to import the module
  4. If module is missing, the script fails after user interaction

Compare with #Requires:

#Requires -Modules SqlServer

param(
    [Parameter(Mandatory)]
    [string]$DatabaseServer
)

# User is never prompted if SqlServer is missing - script fails immediately

Error handling capabilities

Import-Module allows granular error handling, while #Requires provides all-or-nothing validation:

# Conditional module loading
try {
    Import-Module OptionalFeatureModule -ErrorAction Stop
    $advancedFeatures = $true
} catch {
    Write-Warning "Advanced features unavailable"
    $advancedFeatures = $false
}

# Continue with basic functionality
Process-BasicOperations

Automatic module loading

#Requires automatically imports specified modules if they are available, eliminating redundant import statements:

#Requires -Modules PSReadLine, Pester

# Modules are already loaded - no Import-Module needed
$history = Get-PSReadLineOption
Invoke-Pester

Common #Requires parameters

PowerShell supports several requirement types:

  • -Version: Minimum PowerShell version
  • -Modules: Required PowerShell modules (with optional version specifications)
  • -PSEdition: Desktop or Core edition requirement
  • -RunAsAdministrator: Elevation requirement

Example with version constraints:

#Requires -Version 7.2
#Requires -Modules @{ ModuleName = 'Az.Storage'; ModuleVersion = '4.0.0' }
#Requires -Modules @{ ModuleName = 'SqlServer'; MaximumVersion = '21.1.18' }

Best Practices and Use Cases

When to use #Requires

Use #Requires for core dependencies that the entire script relies upon:

#Requires -Version 7.0  # Script uses ternary operators
#Requires -Modules ActiveDirectory  # All operations involve AD

# Script assumes these are available throughout
$users = Get-ADUser -Filter *
$enabled = $users.Enabled ? "Active" : "Inactive"

When to use Import-Module

Reserve Import-Module for optional features, conditional logic, or dynamic requirements:

# Platform-specific modules
if ($IsWindows) {
    Import-Module ActiveDirectory
} elseif ($IsLinux) {
    Import-Module LinuxAdminTools
}

# Feature flags
if ($EnableReporting) {
    Import-Module ReportingTools -ErrorAction Stop
    Generate-Report
}

Combining both approaches

For complex scripts, combine both strategies:

#Requires -Version 7.0
#Requires -Modules Az.Accounts  # Core requirement

# Additional modules based on parameters
param(
    [switch]$IncludeAnalytics
)

if ($IncludeAnalytics) {
    try {
        Import-Module DataAnalytics -ErrorAction Stop
        Invoke-Analysis
    } catch {
        Write-Warning "Analytics skipped - module unavailable"
    }
}

Potential limitations

Consider these constraints when using #Requires:

  1. Variables not supported - Directives require literal values
# This will NOT work
$requiredVersion = "7.2"
#Requires -Version $requiredVersion  # Error: variable not allowed

# Must use literal value
#Requires -Version 7.2
  1. No conditional requirements - Cannot make requirements dependent on runtime conditions
# This script will fail on Linux/Mac
#Requires -Modules ActiveDirectory, DHCP

# Better approach for cross-platform scripts
#Requires -Version 7.0  # Universal requirement

# Handle platform-specific modules at runtime
if ($IsWindows) {
    Import-Module ActiveDirectory
} else {
    Write-Warning "AD operations not available on this platform"
}

Conclusion

The #Requires directive provides a robust mechanism for enforcing script prerequisites, preventing partial execution failures that could leave systems in inconsistent states. Use it for critical dependencies while reserving Import-Module for optional or conditional features.

References

Do you like this post? Share it with your friends!

You can also subscribe to my RSS channel for future posts.