Skip to main content

PowerShell for Intermediate Users

Author(s): Byrch
Last Updated: 2025‑07‑30
Recommended Prerequisites: None

This content was written with the intermediate audience in mind. For the experienced Powershell users in the community its important to emphasize that all scripts should be tested in a safe environment before being run in production. Additionally, development of scripts should following best practices and modularity to ensure maintainability and reusability despite what environment might be thrown at it.


Learning objectives

By the end, learners should be able to:

  • Write reusable functions and bundle them into modules with help/validation.
  • Inspect and control Windows services and processes safely.
  • Make targeted, reversible registry changes.
  • Manage local users, groups, and permissions with audit trails.
  • Build "automation" that logs actions and supports Powershell best practices.

1) Functions & modules

Minimal, production‑ish function


# Powershell functions follow a verb-noun naming convention, with only a few verbs approved for use.

function Set-StartupType {

# Parameter validation and support for -WhatIf and -Confirm
# CmdletBinding attribute enables advanced function features (and pipeline support)
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
param(
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateSet('Automatic','Manual','Disabled')]
[string]$StartupType,

[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('Name')]
[string[]]$ServiceName
)

# Function body should follow a begin, process, end pattern for structure and organization.
begin { }
process {
foreach ($svc in $ServiceName) {
if ($PSCmdlet.ShouldProcess("service '$svc'", "set startup to $StartupType")) {
try {
Set-Service -Name $svc -StartupType $StartupType -ErrorAction Stop
}
catch { Write-Error "Failed to set $svc: $_" }
}
}
}
}

Turning functions into a module

  • Create a folder named for your module or script. Below is a simple structure for a module named MyTeam.SecurityTools. Such structure is the gold standard for modular and reusable Powershell code. (It also makes readability and maintenance easier :)
MyTeam.SecurityTools\
MyTeam.SecurityTools.psd1 # module manifest (version, author, tags)
MyTeam.SecurityTools.psm1 # exported functions
Private\*.ps1 # helpers
  • Export with Export-ModuleMember.
  • Version your manifest; keep functions idempotent and -WhatIf friendly.

2) Working with services & processes

Example of an auditing of services

$knownGood = 'wuauserv','BITS','WinDefend','LanmanWorkstation','LanmanServer','Dhcp','Dnscache'
Get-Service | Select-Object Name, Status, StartType | Sort-Object Name |
Tee-Object -FilePath "$env:USERPROFILE\Desktop\services-audit.csv" | Out-Host
# Keeping a CSV of current services is a great way to baseline and track changes over time. Additionally, it can be useful for troubleshooting unexpected errors or behaviors.

# Highlight non-standard auto services
Get-Service | Where-Object { $_.StartType -eq 'Automatic' -and $_.Name -notin $knownGood } |
Select Name,DisplayName,Status | Format-Table -AutoSize

Safe scripting tips

  • Prefer Set-Service -StartupType Manual over disabling until you confirm necessity.
$baseline = @{ 'Workstation' = @('WinDefend','wuauserv','BITS'); }
$expected = $baseline.Workstation
Get-Service | Where-Object Name -notin $expected | Export-Csv baseline-delta.csv -NoTypeInformation

3) Registry edits (reversible and targeted)

Always: export before you change

$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
reg.exe export HKLM\SOFTWARE "HKLM_SOFTWARE_$stamp.reg" /y | Out-Null

Common, defensible tweaks

# Show file extensions (reduces double-extension tricks)
Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name HideFileExt -Value 0

# Disable Guest if present
Get-LocalUser -Name 'Guest' -ErrorAction SilentlyContinue | ForEach-Object {
Disable-LocalUser -Name $_.Name
}

Keep a small revert script (or robust change log) alongside your changes.


4) Permissions & Users

Local accounts & groups

# Create a standard user
$u = 'ExampleTestUser'
if (-not (Get-LocalUser -Name $u -ErrorAction SilentlyContinue)) {
$pw = Read-Host -AsSecureString "Enter password for $u"
New-LocalUser -Name $u -Password $pw -AccountNeverExpires:$true -FullName 'Student Standard'
}

# Add new user to group
Add-LocalGroupMember -Group 'Users' -Member $u -ErrorAction SilentlyContinue

# Review admins
$approvedAdmins = @('Administrator','SusAdmin')
(Get-LocalGroupMember 'Administrators').Name |
Where-Object { $_ -notin $approvedAdmins } |
ForEach-Object { Write-Output "Review admin member: $_" }

NTFS ACLs

$path = 'C:\Shared'
if (-not (Test-Path $path)) { New-Item -ItemType Directory -Path $path | Out-Null }
$acl = Get-Acl $path
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule('Users','Modify','ContainerInherit,ObjectInherit','None','Allow')
$acl.SetAccessRule($rule)
Set-Acl -Path $path -AclObject $acl

- It is important to note that some cmdlets used in this section may require recent versions of Windows Powershell (or Powershell Core).

- Please ensure the support of these commandlets in your environment before using them. (i.e. Get-LocalUser, Get-LocalGroupMember, Add-LocalGroupMember will not work in Microsoft Windows 7 Powershell)


5) Now that we have the scripting basics down, let's look at what more we can do with Powershell. Take a look at all these cmdlets!

Management Module

Get-Command -Module Microsoft.PowerShell.Management | Out-GridView

Users

Get-Command -Noun *User | Out-GridView

Services

Get-Command -Noun *Service | Out-GridView

Processes

Get-Command -Noun *Process | Out-GridView

Registry

Get-Command -Noun *ItemProperty | Out-GridView

Firewall

Get-Command -Noun *Firewall* | Out-GridView

Scheduled Tasks

Get-Command -Noun *ScheduledTask* | Out-GridView

Transcription (Logging)

Get-Command -Noun *Transcript* | Out-GridView

9) Comment‑based help template

<#!
.SYNOPSIS
Sets a service startup type with WhatIf/Confirm support.
.DESCRIPTION
Safe wrapper around Set-Service for baseline enforcement.
.PARAMETER ServiceName
One or more service names.
.PARAMETER StartupType
Automatic, Manual, or Disabled.
.EXAMPLE
'WinDefend' | Set-StartupType -StartupType Automatic -WhatIf
#>

10) Quick reference (cheat sheet) (more cmdlets to take a look at)

  • List services: Get-Service | Sort Name
  • Change startup: Set-Service -Name <svc> -StartupType Manual
  • Running processes: Get-Process | Sort CPU -Desc | Select -First 10
  • Local users: Get-LocalUser / Enable-LocalUser / Disable-LocalUser
  • Local groups: Get-LocalGroupMember Administrators
  • Registry provider: Get-Item 'HKLM:\...' / Set-ItemProperty
  • Firewall: Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled True
  • Logging: Start-Transcript / Stop-Transcript
  • Scheduled task: Register-ScheduledTask

12) Notes & guardrails

  • Use -WhatIf first; only remove or disable after documenting a reason.
  • Create a restore point if available: Checkpoint-Computer -Description 'BeforeHygiene' (requires System Protection).
  • Avoid blanket disabling of services; prefer Manual unless you’ve confirmed.
  • Keep all changes reversible and logged to a dated folder on the Desktop.

Attribution

You may copy/adapt with attribution.