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
-WhatIffriendly.
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 Manualover 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
-WhatIffirst; 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.