PowerShell sysadmin tools for Windows. The flagship is the SysUtils
module on the PowerShell Gallery (currently exporting
Get-DllInfo). The other three tools — process monitor and two registry
browsers — still ship as standalone .ps1 scripts in this repo and will
move into the module in subsequent releases.
Built and tested against Windows PowerShell 5.1 on Windows 10 / Server 2016 and newer. No external dependencies.
# From any Windows machine with PowerShell 5.1+:
Install-Module SysUtils -Force
Get-DllInfo C:\Windows\System32\scrrun.dll | ConvertTo-Json -Depth 12That's it — PSGallery is registered by default, no Register-PSRepository
needed.
Pristine Windows 10 1809 / Server 2019 boxes ship with a stock
PowerShellGet 1.0.0.1 that does not pull TLS 1.2 by default and lacks
the NuGet provider. Install-Module fails with
NuGet provider is required until you run this once per box:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted # optional, skips the per-install confirmationAfter that, Install-Module SysUtils -Force works as documented above.
Newer Windows builds (Server 2022, Win10 21H2+, Win11) already have the
NuGet provider preinstalled.
Inspects DLL, OCX, EXE and SYS files without loading them into the process
(no LoadLibrary, no DllMain execution). Works cross-bitness — a 32-bit
DLL in SysWOW64 can be inspected from a 64-bit PowerShell session and
vice versa.
Layered output, all opt-in via switches; emits a single PSCustomObject
suitable for ConvertTo-Json (Ansible-friendly):
| Switch | What it adds |
|---|---|
| (default) | PE header (architecture, subsystem, characteristics, sections, timestamp), version info, COM and .NET detection |
-IncludeImports |
IDT walk: per-module function lists, including import-by-ordinal |
-IncludeExports |
Full export table with forwarder detection (e.g. kernel32 -> NTDLL.Rtl*) |
-IncludeResources |
Recursive 3-level walk of the resource tree |
-IncludeTypeLib |
TypeLib reader via oleaut32!LoadTypeLibEx (REGKIND_NONE) — CoClasses, interfaces, methods, parameters, enums, aliases, IIDs/CLSIDs |
-IncludeDotNetTypes |
Assembly.ReflectionOnlyLoadFrom — [ComVisible] / [Guid] / [ProgId] per type, full type listing |
-IncludeSignature |
Authenticode signature info |
-IncludeHash |
SHA-256 of the file |
-Detailed |
Convenience: enables every Include* switch above |
For managed assemblies, the cheap path also reports a PEKind field that
disambiguates AnyCPU from real x86 / x64 / AnyCPUPrefer32 /
ManagedMixed (which the raw Machine field cannot do alone).
# One DLL, full report as JSON
Get-DllInfo C:\Windows\System32\scrrun.dll -Detailed | ConvertTo-Json -Depth 12
# Find every COM-registrable DLL under a directory
Get-ChildItem C:\Legacy -Include *.dll,*.ocx -Recurse |
Get-DllInfo |
Where-Object { $_.Com.IsComServer } |
Select-Object Path, @{n='HasTLB';e={$_.Com.HasTypeLib}},
@{n='Arch';e={$_.PE.Architecture}}
# Audit which DLLs import a given API
Get-ChildItem C:\App -Filter *.dll |
Get-DllInfo -IncludeImports |
Where-Object { $_.Imports.Functions.Name -contains 'CreateRemoteThread' } |
Select-Object PathDll-Inspector.ps1 at the repo root is a thin wrapper that imports the
module and forwards every parameter to Get-DllInfo, so users who
git clone the repo can keep running .\Dll-Inspector.ps1 unchanged.
Live activity monitor for a binary (by name or PID). Subscribes to WMI process creation/termination events and polls each tracked PID for loaded modules and network connections. If Sysmon is installed on the target, also tails its file/registry/DNS/image-load events. Follows child processes automatically. Works locally or remotely via WinRM.
# Follow every notepad.exe on the local machine
.\Process-Monitor.ps1 -Target notepad.exe
# Follow PID 1234 on a remote host
$c = Import-Clixml .\admin.xml
.\Process-Monitor.ps1 -Target 1234 -ComputerName host.example -Credential $cLightweight REPL to browse registry keys and values. Local mode talks to
the local registry directly; remote mode dispatches each command via a
persistent PSSession over WinRM.
# Local
.\Registry-Navigator.ps1
# Remote
$cred = Get-Credential administrator
.\Registry-Navigator.ps1 -ComputerName host.example -Credential $credTwo-pane TUI (subkeys | values) using the Windows console API.
Arrow-key navigation, Enter to descend, Backspace to go up. Same remote
WinRM mode as Registry-Navigator.ps1.
.\Registry-TUI.ps1
.\Registry-TUI.ps1 -ComputerName host.example -Credential (Get-Credential)Every script ships full comment-based help:
Get-Help .\Dll-Inspector.ps1 -Full
Get-Help .\Process-Monitor.ps1 -ExamplesGet-DllInfo lives in the SysUtils module. The PSGallery repository
is registered by default on every Windows with PowerShellGet, so a single
command on each server is enough:
Install-Module SysUtils -Force
Get-DllInfo C:\Windows\System32\scrrun.dllFor the standalone scripts that haven't moved into the module yet
(Process-Monitor.ps1, Registry-Navigator.ps1, Registry-TUI.ps1),
fall back to git clone (Option B) or direct download (Option C).
The simplest path if git is installed on the targets. Includes both the
module and the standalone scripts. git pull to update.
git clone https://github.com/manuel-alcocer/powershell-sysutils.git C:\Tools\powershell-sysutils
cd C:\Tools\powershell-sysutils
# Run a standalone script directly:
.\Dll-Inspector.ps1 -Path .\anything.dll
# Or import the module from the cloned tree (no Install-Module):
Import-Module .\SysUtils\SysUtils.psd1 -Force
Get-DllInfo .\anything.dllPull individual scripts from raw.githubusercontent.com. Useful for one-off
runs or for bootstrapping.
$base = 'https://raw.githubusercontent.com/manuel-alcocer/powershell-sysutils/main'
foreach ($s in 'Dll-Inspector.ps1','Process-Monitor.ps1','Registry-Navigator.ps1','Registry-TUI.ps1') {
Invoke-WebRequest "$base/$s" -OutFile "C:\Tools\$s" -UseBasicParsing
}Three patterns from a control node. Pick whichever fits your playbook.
win_psmodule — installs the module from PowerShell Gallery. Cleanest:
- hosts: windows
tasks:
- name: Install SysUtils module
community.windows.win_psmodule:
name: SysUtils
state: present
accept_license: yeswin_get_url — pulls individual scripts over HTTP, no git required on the
target. Use this for the standalone scripts not yet in the module:
- hosts: windows
vars:
repo_base: https://raw.githubusercontent.com/manuel-alcocer/powershell-sysutils/main
install_dir: C:\Tools\powershell-sysutils
scripts:
- Dll-Inspector.ps1
- Process-Monitor.ps1
- Registry-Navigator.ps1
- Registry-TUI.ps1
tasks:
- name: Ensure install dir exists
ansible.windows.win_file:
path: "{{ install_dir }}"
state: directory
- name: Download scripts
ansible.windows.win_get_url:
url: "{{ repo_base }}/{{ item }}"
dest: "{{ install_dir }}\\{{ item }}"
force: yes
loop: "{{ scripts }}"win_command — clone the repo if you have git for Windows on the
targets and want a versioned working tree:
- name: Clone powershell-sysutils
ansible.windows.win_command:
cmd: git clone --depth 1 https://github.com/manuel-alcocer/powershell-sysutils.git C:\Tools\powershell-sysutils
creates: C:\Tools\powershell-sysutils\.gitTo call any script from a playbook and consume its output as structured
data, run it through ConvertTo-Json and parse on the control node:
- name: Inspect a DLL
ansible.windows.win_powershell:
script: |
C:\Tools\powershell-sysutils\Dll-Inspector.ps1 `
-Path C:\Windows\System32\scrrun.dll -Detailed |
ConvertTo-Json -Depth 12 -Compress
register: dll_infoIf your servers refuse to run scripts because of ExecutionPolicy, run
them with the Bypass flag — it does not persist beyond the call:
powershell -NoProfile -ExecutionPolicy Bypass -File .\Dll-Inspector.ps1 -Path foo.dllReleases are tag-driven via GitHub Actions:
| Workflow | Trigger | What it does |
|---|---|---|
.github/workflows/ci.yml |
every push / PR | Test-ModuleManifest + import + smoke test of Get-DllInfo against kernel32.dll, scrrun.dll (with -IncludeTypeLib) and a managed assembly. Catches regressions before tagging. |
.github/workflows/publish.yml |
push of tag v*.*.* (or manual workflow_dispatch) |
Asserts the tag version matches ModuleVersion in SysUtils.psd1, re-runs the smoke tests, then Publish-Module to PowerShell Gallery and creates a GitHub Release. |
One-time setup: create the repository secret PSGALLERY_API_KEY under
Settings → Secrets and variables → Actions → New repository secret, with a
key obtained from
powershellgallery.com → My Account → API Keys.
Cutting a release:
# 1. Bump ModuleVersion in SysUtils/SysUtils.psd1
# 2. Update ReleaseNotes in the same file
git commit -am 'Release v1.0.1'
git push
# 3. Tag and push — Actions does the rest
git tag v1.0.1
git push --tagsVersions on the Gallery are immutable — once 1.0.0 is published, that
exact .nupkg cannot be replaced. Bump for any fix.
Build-Module.ps1 at the repo root is kept for local validation only
(.\Build-Module.ps1 -WhatIfOnly is handy before tagging) and as an
emergency manual publish path. The CI flow is the canonical one.
MIT — see LICENSE.