Skip to content

Your First Script

Note

Step-by-step tutorial for creating your first PowerShell script from scratch, covering everything from basics to best practices.

Overview

This tutorial will walk you through creating a complete, practical PowerShell script. You'll learn how to:

  • Set up a script file
  • Accept user input
  • Process data
  • Handle errors
  • Save output
  • Run your script

We'll build a File Organizer Script that sorts files into folders based on their extension - a real-world task you'll actually use!

What You'll Build

A script that: 1. Asks the user for a folder path 2. Groups files by extension (.txt, .pdf, .jpg, etc.) 3. Creates folders for each extension 4. Moves files into their respective folders 5. Reports what was organized

Step 1: Create the Script File

Create a New .ps1 File

# Option 1: Use PowerShell to create the file
New-Item -Path "C:\Scripts\Organize-Files.ps1" -ItemType File

# Option 2: Use your favorite text editor
# Create a new file called "Organize-Files.ps1"
# Save it in a folder like C:\Scripts\

Naming Convention

  • Use Verb-Noun format (e.g., Organize-Files, Get-Report, Set-Config)
  • Use .ps1 extension for PowerShell scripts
  • Avoid spaces in file names (use hyphens or CamelCase)

Set Execution Policy (If Needed)

# Check current execution policy
Get-ExecutionPolicy

# If it's "Restricted", you can't run scripts
# Set it to RemoteSigned to allow local scripts
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Step 2: Add the Script Header

Start your script with comments that explain what it does:

<#
.SYNOPSIS
    Organizes files in a directory by moving them into folders based on their extension.

.DESCRIPTION
    This script scans a specified directory, groups files by their extension,
    creates folders for each extension type, and moves the files accordingly.

.PARAMETER Path
    The path to the directory containing files to organize.

.EXAMPLE
    .\Organize-Files.ps1 -Path "C:\Downloads"

.NOTES
    Author: Your Name
    Created: 2025-12-27
    Version: 1.0
#>

Why Add Comments?

  • Helps you remember what the script does months later
  • Makes it easier for others to understand your code
  • Enables Get-Help to show information about your script

Step 3: Add Parameters

Accept input from the user when they run the script:

param(
    [Parameter(Mandatory = $false)]
    [string]$Path,

    [Parameter(Mandatory = $false)]
    [switch]$WhatIf
)

Explanation:

  • param() defines what inputs the script accepts
  • [Parameter(Mandatory = $false)] - this input is optional
  • [string]$Path - a text parameter called "Path"
  • [switch]$WhatIf - an on/off flag (no value needed)

Step 4: Validate Input

Check if the user provided a path, and verify it exists:

# If no path provided, ask for one
if (-not $Path) {
    $Path = Read-Host "Enter the path to organize"
}

# Verify the path exists
if (-not (Test-Path -Path $Path)) {
    Write-Error "Path not found: $Path"
    exit 1  # Exit with error code
}

# Verify it's a directory, not a file
$item = Get-Item -Path $Path
if (-not $item.PSIsContainer) {
    Write-Error "Path must be a directory, not a file"
    exit 1
}

Always Validate Input

  • Check if required data is provided
  • Verify paths exist before using them
  • Confirm data is the right type (file vs folder, number vs text)

Step 5: Main Processing Logic

Get files and group them by extension:

# Get all files (not directories) from the path
Write-Output "Scanning $Path for files..."
$files = Get-ChildItem -Path $Path -File

# Report what we found
Write-Output "Found $($files.Count) files to organize"

# Group files by extension
$fileGroups = $files | Group-Object -Property Extension

# Report the groups
Write-Output "`nFile breakdown:"
foreach ($group in $fileGroups) {
    $extName = if ($group.Name) { $group.Name } else { "No Extension" }
    Write-Output "  $extName : $($group.Count) files"
}

Step 6: Create Folders and Move Files

# Process each group
foreach ($group in $fileGroups) {
    # Determine folder name
    $folderName = if ($group.Name) {
        $group.Name.TrimStart('.')  # Remove the leading dot from extension
    } else {
        "NoExtension"
    }

    # Create full folder path
    $targetFolder = Join-Path -Path $Path -ChildPath $folderName

    # Create folder if it doesn't exist
    if (-not (Test-Path -Path $targetFolder)) {
        Write-Output "`nCreating folder: $folderName"
        if (-not $WhatIf) {
            New-Item -Path $targetFolder -ItemType Directory | Out-Null
        }
    }

    # Move files
    Write-Output "Moving $($group.Count) $folderName files..."
    foreach ($file in $group.Group) {
        $destination = Join-Path -Path $targetFolder -ChildPath $file.Name

        if ($WhatIf) {
            Write-Output "  [WHATIF] Would move: $($file.Name)"
        } else {
            Move-Item -Path $file.FullName -Destination $destination
            Write-Output "  Moved: $($file.Name)"
        }
    }
}

Step 7: Add Error Handling

Wrap the main logic in try/catch to handle errors gracefully:

try {
    # Get all files (not directories) from the path
    Write-Output "Scanning $Path for files..."
    $files = Get-ChildItem -Path $Path -File -ErrorAction Stop

    # ... rest of the processing logic ...

} catch {
    Write-Error "An error occurred: $_"
    Write-Error $_.Exception.Message
    exit 1
} finally {
    Write-Output "`nScript completed!"
}

The Complete Script

Here's the full script put together:

<#
.SYNOPSIS
    Organizes files in a directory by moving them into folders based on their extension.

.DESCRIPTION
    This script scans a specified directory, groups files by their extension,
    creates folders for each extension type, and moves the files accordingly.

.PARAMETER Path
    The path to the directory containing files to organize.

.PARAMETER WhatIf
    Preview what would happen without actually moving files.

.EXAMPLE
    .\Organize-Files.ps1 -Path "C:\Downloads"

.EXAMPLE
    .\Organize-Files.ps1 -Path "C:\Downloads" -WhatIf

.NOTES
    Author: Your Name
    Created: 2025-12-27
    Version: 1.0
#>

param(
    [Parameter(Mandatory = $false)]
    [string]$Path,

    [Parameter(Mandatory = $false)]
    [switch]$WhatIf
)

# Validate input
if (-not $Path) {
    $Path = Read-Host "Enter the path to organize"
}

if (-not (Test-Path -Path $Path)) {
    Write-Error "Path not found: $Path"
    exit 1
}

$item = Get-Item -Path $Path
if (-not $item.PSIsContainer) {
    Write-Error "Path must be a directory, not a file"
    exit 1
}

# Main logic with error handling
try {
    # Get all files
    Write-Output "Scanning $Path for files..."
    $files = Get-ChildItem -Path $Path -File -ErrorAction Stop

    if ($files.Count -eq 0) {
        Write-Output "No files found to organize."
        exit 0
    }

    Write-Output "Found $($files.Count) files to organize"

    # Group by extension
    $fileGroups = $files | Group-Object -Property Extension

    # Show breakdown
    Write-Output "`nFile breakdown:"
    foreach ($group in $fileGroups) {
        $extName = if ($group.Name) { $group.Name } else { "No Extension" }
        Write-Output "  $extName : $($group.Count) files"
    }

    # Process each group
    foreach ($group in $fileGroups) {
        # Determine folder name
        $folderName = if ($group.Name) {
            $group.Name.TrimStart('.')
        } else {
            "NoExtension"
        }

        # Create full folder path
        $targetFolder = Join-Path -Path $Path -ChildPath $folderName

        # Create folder if needed
        if (-not (Test-Path -Path $targetFolder)) {
            Write-Output "`nCreating folder: $folderName"
            if (-not $WhatIf) {
                New-Item -Path $targetFolder -ItemType Directory | Out-Null
            }
        }

        # Move files
        Write-Output "Moving $($group.Count) $folderName files..."
        foreach ($file in $group.Group) {
            $destination = Join-Path -Path $targetFolder -ChildPath $file.Name

            if ($WhatIf) {
                Write-Output "  [WHATIF] Would move: $($file.Name)"
            } else {
                try {
                    Move-Item -Path $file.FullName -Destination $destination -ErrorAction Stop
                    Write-Output "  Moved: $($file.Name)"
                } catch {
                    Write-Warning "  Failed to move $($file.Name): $_"
                }
            }
        }
    }

    Write-Output "`nOrganization complete!"

} catch {
    Write-Error "An error occurred: $_"
    Write-Error $_.Exception.Message
    exit 1
} finally {
    Write-Output "`nScript finished."
}

How to Run Your Script

Option 1: Run from PowerShell

# Navigate to the script directory
cd C:\Scripts

# Run the script with a parameter
.\Organize-Files.ps1 -Path "C:\Downloads"

# Preview without making changes
.\Organize-Files.ps1 -Path "C:\Downloads" -WhatIf

# Run without parameter (will prompt)
.\Organize-Files.ps1

Option 2: Run from File Explorer

# Right-click the .ps1 file
# Select "Run with PowerShell"

# Note: This may not work if execution policy is restrictive

Option 3: Run with Full Path

# From any location
& "C:\Scripts\Organize-Files.ps1" -Path "C:\Downloads"

Testing Your Script

Always test with -WhatIf first!

# Test without making changes
.\Organize-Files.ps1 -Path "C:\Test" -WhatIf

# Review the output to see what WOULD happen
# If it looks good, run without -WhatIf
.\Organize-Files.ps1 -Path "C:\Test"

Always Test First

  • Use -WhatIf to preview changes
  • Test on a small sample folder first
  • Make backups of important data before running scripts

Improving Your Script

Add Logging

# Add at the start of your script
$logPath = "C:\Logs\Organize-Files-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
Start-Transcript -Path $logPath

# Your script here...

# Add at the end
Stop-Transcript

Add Progress Indicators

# Show progress when processing many files
$counter = 0
foreach ($file in $files) {
    $counter++
    Write-Progress -Activity "Organizing files" `
                   -Status "Processing $($file.Name)" `
                   -PercentComplete (($counter / $files.Count) * 100)

    # Process file...
}

Add Verbose Output

# Change Write-Output to Write-Verbose for detailed messages
Write-Verbose "Processing file: $($file.Name)" -Verbose

# Run with -Verbose to see these messages
.\Organize-Files.ps1 -Path "C:\Downloads" -Verbose

Common Script Patterns

Pattern 1: Validate, Process, Report

# 1. Validate input
if (-not (Test-Path $Path)) { exit }

# 2. Process data
$results = Get-ChildItem $Path | Where-Object {...}

# 3. Report results
Write-Output "Processed $($results.Count) items"

Pattern 2: Try-Catch-Finally

try {
    # Main logic that might fail
    $data = Get-Content $FilePath
} catch {
    # Handle errors
    Write-Error $_
} finally {
    # Always runs (cleanup)
    Write-Output "Done"
}

Pattern 3: Pipeline Processing

Get-ChildItem -Path $Path |
    Where-Object {$_.Length -gt 1MB} |
    ForEach-Object {
        # Process each item
        Move-Item $_.FullName -Destination $TargetPath
    }

Next Steps

Now that you've created your first script:

  1. Experiment - Modify the script to do something different
  2. Add features - What if you wanted to organize by size instead? Or date?
  3. Learn more - Explore the related topics below
  4. Share - Put your scripts on GitHub to share with others

Common Mistakes

Forgetting to Save Changes

# If you edit a .ps1 file in an editor:
# 1. Make changes
# 2. SAVE THE FILE (Ctrl+S)
# 3. Then run it

# Common mistake: Running old version because you didn't save!

Running from the Wrong Directory

# BAD - PowerShell can't find the script
Organize-Files.ps1

# GOOD - Use .\ to run from current directory
.\Organize-Files.ps1

# GOOD - Use full path
C:\Scripts\Organize-Files.ps1

Hardcoding Paths

# BAD - Only works on your computer
$path = "C:\Users\Raymond\Documents"

# GOOD - Use parameters or environment variables
param([string]$Path)
# or
$path = $env:USERPROFILE + "\Documents"

Tips & Tricks

Use ISE or VS Code

# PowerShell ISE (built-in)
ise "C:\Scripts\Organize-Files.ps1"

# VS Code (recommended, free download)
code "C:\Scripts\Organize-Files.ps1"

# Benefits:
# - Syntax highlighting
# - IntelliSense (autocomplete)
# - Debugging tools
# - Error detection

Test Parameters Separately

# Before putting code in a script, test it interactively
$Path = "C:\Test"
$files = Get-ChildItem -Path $Path -File
$files.Count

# Once it works, add it to your script

Use Descriptive Variable Names

# BAD - unclear what these mean
$f = Get-ChildItem
$x = 100

# GOOD - clear and readable
$files = Get-ChildItem
$fileSizeLimit = 100MB

Common Mistakes

Not Saving Your Script Before Running

# Beginner mistake: Edit script, press F5, wonder why changes don't work
# You MUST save (Ctrl+S) before running!

# VS Code: Look for the dot in the tab title
# •MyScript.ps1 = unsaved changes
# MyScript.ps1 = saved

Running Wrong Version of Script

# BAD - Running old script from different location
C:\Old\MyScript.ps1
# You're editing C:\Scripts\MyScript.ps1 but running the old one!

# GOOD - Always verify you're running the right file
# Check $PSScriptRoot or use full path
& "C:\Scripts\MyScript.ps1"

Forgetting Execution Policy

# First time running scripts, you'll get:
# "...cannot be loaded because running scripts is disabled"

# Fix once:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Then your scripts will run

Not Using Parameters

# BAD - Hardcoded values
$path = "C:\Temp"
Get-ChildItem $path

# GOOD - Use parameters for flexibility
param([string]$Path = "C:\Temp")
Get-ChildItem $Path

# Now you can:
.\MyScript.ps1 -Path "C:\Data"

Additional Resources