How-Tos

How to Run a PowerShell Script on Multiple Computers

To run powershell script on multiple computers reliably, you need to know which method breaks on offline machines, workgroups, and Mac endpoints.

Mountain landscape representing leadership perspective and vision
Written by
Trio Content Team
Published on
17 Sep 2026
Modified on
17 Sep 2026

You have a script that works perfectly on one machine. Now you need it on thirty. The instinct is to wrap it in a loop and point it at a list of hostnames, and technically, that works. But none of the three main methods for doing this "just work" across every fleet type without some setup first.

The PowerShell-native way to run a powershell script on multiple computers is Invoke-Command. It fans out to 32 machines concurrently by default and returns structured results with a PSComputerName property on every object. For machines where WinRM isn't configured yet, PsExec is the SMB-based alternative that gets you in without touching each box first.

Both methods have hard stops that most guides skip over. WinRM silently fails on machines with a public network profile; remote workers on home Wi-Fi or hotel networks hit a firewall rule that restricts connections to the same subnet, even with VPN active. PsExec requires SMB ports open and gets flagged by AV software in organizations with active security tooling. GPO adds a third path for domain environments, but it cannot run scripts immediately. Each method has real prerequisites.

This article covers how to set up each method with ready-to-run scripts, how to handle offline machines and capture a structured success/fail report, what each method breaks on so you can choose the right one for your environment, and when a dedicated tool makes more sense than scripting.

TL;DR

TL;DR
  • Invoke-Command -ComputerName already runs against up to 32 machines in parallel — you do not need to add ForEach-Object -Parallel on top of it.

  • WinRM must be running on every target machine. If it isn't, use PsExec to bootstrap it first: PsExec64.exe \\PCName -s powershell Enable-PSRemoting -Force.

  • Machines on public network profiles (home Wi-Fi, hotels) block WinRM connections even if WinRM is enabled. SSH remoting is the fix for off-network endpoints.

  • Always pre-check reachability with Test-Connection before running Invoke-Command on a list. Skip this and you get a flood of red timeout errors with no structured log.

  • PsExec gets flagged by AV software and security teams in many organizations. Have a plan before you use it in a managed environment.

  • For workgroup environments (no Active Directory), PowerShell remoting requires TrustedHosts configuration and explicit credentials on every call — or an MDM/RMM is the practical path forward.

  • Remote sessions are non-interactive. Installers launched via Invoke-Command must use silent install switches or they will hang indefinitely.

How PowerShell Remote Execution Works

If you already know what WinRM is and have used Invoke-Command before, skip ahead to the Three Ways section below.

The standard remote scripting topology is called a fan-out: one admin machine pushes commands to many remote machines at the same time. This is what invoke-command multiple computers is built for. It is not a loop under the hood, it opens concurrent connections to all targets simultaneously, up to its default limit. Understanding how remote script execution works at this architectural level saves a lot of troubleshooting time when connections start failing.

For that fan-out to work, each remote machine needs an authenticated transport layer in place first. WinRM (Windows Remote Management) runs on port 5985 over HTTP using the WS-Management protocol. Traffic is encrypted, but the connection is vulnerable to man-in-the-middle attacks without HTTPS. Enable it with Enable-PSRemoting -Force run locally on the target machine. SSH (port 22) is the cross-platform alternative, available since PowerShell 6.0, it's the only transport that works across Windows, macOS, and Linux.

There is a catch, and it has a name in the community: the bootstrap problem, or the chicken-and-egg problem. You cannot use PowerShell remoting to enable WinRM on a machine where WinRM isn't running yet, the command has no transport to travel on. This is the problem the article will solve with each method.

One more thing before you run commands. Invoke-Command returns deserialized objects; they carry a PSComputerName property you can use for filtering and reporting, but they lose all methods. If your script block returns an object and you need to call a method on it, do that inside the remote -ScriptBlock, not on your local machine after the fact.

Three Ways to Run a PowerShell Script on Multiple Computers

All three methods below are free and built into Windows or available as a free download. Each one also requires infrastructure prerequisites and manual result-handling that a managed solution handles by default — something worth factoring in before committing to a method. Each requires a different infrastructure prerequisite, and picking the wrong one for your environment means going back to square one before you run a single command. Review the comparison table after this section to match a method to your fleet setup.

Method 1 - Invoke-Command (WinRM Required)

This is the PowerShell-native path. WinRM must be enabled on every target machine before you start, run Enable-PSRemoting -Force locally on each one, push it via GPO for domain fleets, or use the PsExec bootstrap covered in Method 2.

Invoke-Command fans out to 32 concurrent connections by default. This means it can run powershell script on multiple computers simultaneously across up to 32 machines without any extra configuration on your part. Do not wrap it in ForEach-Object -Parallel — Invoke-Command is already parallel, and adding ForEach-Object -Parallel on top is redundant and adds runspace overhead. For fleets larger than 32, set -ThrottleLimit to the total machine count.

The $using: scope modifier is required any time you pass a local variable into the remote script block. Variables defined in your local session do not exist inside the remote scope, a missing $using: prefix causes silent failures or misleading "file not found" errors.

Three ready-to-run input patterns:

  • Inline list: Invoke-Command -ComputerName PC1,PC2,PC3 -ScriptBlock { Get-Service wuauserv }
  • Text file: Invoke-Command -ComputerName (Get-Content C:\computers.txt) -ScriptBlock { Get-Service wuauserv }
  • CSV import (property expansion required): Invoke-Command -ComputerName (Import-Csv C:\computers.csv | Select-Object -ExpandProperty ComputerName) -ScriptBlock { Get-Service wuauserv } — the Select-Object -ExpandProperty step is not optional. Without it, Import-Csv returns objects, not a flat array of names, and -ComputerName gets a null argument.

Handling offline machines (the reachability + result capture pattern):

$computers = Get-Content C:\computers.txt
$results = foreach ($comp in $computers) {
    if (Test-Connection -ComputerName $comp -Count 1 -Quiet) {
        try {
            $output = Invoke-Command -ComputerName $comp -ScriptBlock { Get-Service wuauserv } -ErrorAction Stop
            [pscustomobject]@{ ComputerName = $comp; Status = 'OK'; Result = $output.Status }
        } catch {
            [pscustomobject]@{ ComputerName = $comp; Status = 'Error'; Result = $_.Exception.Message }
        }
    } else {
        [pscustomobject]@{ ComputerName = $comp; Status = 'Unreachable'; Result = 'n/a' }
    }
}
$results | Export-Csv C:\results.csv -NoTypeInformation

Skipping the Test-Connection pre-check produces what practitioners call "the red wall", four lines of PSRemotingTransportException output per unreachable machine, no structured log, and a 21-second timeout wait per machine in a mixed-availability list.

Restart-Computer use case — for a powershell script to restart multiple remote computers:

Restart-Computer -ComputerName (Get-Content C:\computers.txt) -Force -Wait -For PowerShell -Timeout 300 -Delay 10

-Wait holds the script until each machine completes its restart. -For PowerShell waits until PowerShell is available on the restarted machine before releasing. In PowerShell 7.1, Restart-Computer gained Linux and macOS support via /sbin/shutdown for mixed-OS deployments, though on non-Windows platforms only WhatIf, Confirm, and common parameters are supported.

Troubleshooting: If Invoke-Command returns results for some machines but not others, check whether the missing machines have a public network profile. WinRM's default firewall rule restricts connections to the same subnet on public profiles — a laptop on home Wi-Fi will block the connection even if WinRM is running.

Second-order consequence: After enabling WinRM fleet-wide, audit your firewall rules. The default WinRM exception opens port 5985 to all source IPs on domain and private profiles, which may be broader than your security policy intends.

  • ✅ Native PowerShell
  • ✅ Already parallel to 32 machines by default
  • ✅ Returns structured objects with PSComputerName
  • ✅ Supports SSH transport for cross-platform use (PowerShell 7+)
  • ❌ WinRM must be pre-configured on every target
  • ❌ Fails on machines with public network profiles (remote workers)
  • ❌ No persistent queue — offline machines are skipped, not retried

Method 2 - PsExec (SMB Required, WinRM Optional)

PsExec is a free Sysinternals tool from Microsoft. No installation is needed — extract PsExec.exe from PSTools.zip to C:\Windows\System32 and it's ready. It requires TCP/445 and UDP/137 (named pipes over SMB) and runs commands on remote machines without WinRM.

The primary reason to know PsExec even if you plan to use Invoke-Command long-term is the bootstrap use case. PsExec can enable WinRM on machines that don't have it yet:

PsExec64.exe \\PCName -s powershell Enable-PSRemoting -Force

Once WinRM is confirmed running, you can switch to Invoke-Command for all subsequent fleet operations. PsExec becomes a one-time enablement tool rather than an ongoing dependency.

For running a script across psexec multiple computers directly:

# Comma-separated list
psexec \\PC1,PC2,PC3 -s powershell -ExecutionPolicy Bypass -File \\share\script.ps1

# From a text file
psexec @computers.txt -s powershell -ExecutionPolicy Bypass -File \\share\script.ps1

PsExec can also run Command Prompt on a remote computer for an interactive session — useful when you need to troubleshoot a machine directly without RDP.

PsExec drops PSEXESVC.exe on the target machine at each invocation and removes it on clean completion. If you interrupt a command with Ctrl+C, the service stub persists until the next successful PsExec run to that machine or manual cleanup. Security teams actively hunt for this artifact as an indicator of lateral movement.

PsExec is classified under MITRE ATT&CK T1569.002, and many AV solutions flag it as a LOTL attack tool. Security teams in organizations with active tooling often block it via AppLocker or firewall rules. If your security team has policies around PsExec, the bootstrap workaround above is still valuable as a one-time enablement step — after which you switch to Invoke-Command for ongoing use. The non-technical bottleneck is real: some admins are in a position where the security team blocks PsExec AND they lack the authority to enable WinRM fleet-wide without a formal change request. Plan for that governance step before you depend on either tool.

PsExec writes to stdout/stderr only, no native [pscustomobject] output. Redirect with > log.txt for the closest thing to a report.

  • ✅ Works without WinRM pre-configured
  • ✅ Only tool that can bootstrap WinRM remotely over SMB
  • ✅ No installation needed on target machines
  • ❌ Windows-only
  • ❌ Flagged by AV and security tooling in many environments
  • ❌ No structured per-machine result output
  • ❌ Leaves PSEXESVC.exe on targets if interrupted

Method 3 - Group Policy (Domain-Only)

GPO requires Active Directory domain membership. It will not work in workgroup environments — there is no central push mechanism without a domain controller.

Two approaches exist within GPO. Startup/logon scripts run at the next policy refresh cycle, which means they do not run immediately. The more useful option for one-time deployments is a GPO Immediate Scheduled Task: it runs on the next policy refresh as SYSTEM with admin rights, and runs once per refresh cycle rather than once per logon.

The latency problem applies to both approaches. GPO does not reach machines that are offline during the refresh cycle, and there is no native confirmation that the script ran on any specific machine. For urgent one-time tasks, this makes GPO the wrong tool.

  • ✅ No per-machine configuration needed
  • ✅ Scales to any fleet size without manual targeting
  • ❌ Requires Active Directory
  • ❌ Delayed execution; no immediate run option without Immediate Scheduled Task complexity
  • ❌ No native result reporting per machine

Which method should I use to run a PowerShell script on multiple computers?

WinRM is already enabled on all targets, machines are domain-joined or on the same network — use Invoke-Command. It's already parallel to 32 machines and returns structured results without extra tooling.

WinRM is NOT enabled and you need to run a script today without touching each machine — use PsExec to either bootstrap WinRM (then switch to Invoke-Command) or run the script directly via PsExec for a one-time task.

You have a domain and the script doesn't need to run immediately — use a GPO Immediate Scheduled Task for fleet-wide deployment without per-machine targeting.

Your fleet includes Mac or Linux machines, or machines on external networks — use Invoke-Command -SSHConnection (PowerShell 7+ with SSH configured on targets).

Not sure? / Machines are in a workgroup with no AD — the manual setup cost for any free method likely exceeds the value for fleets over ~15 machines. Evaluate an MDM or RMM agent.

Method Comparison: Prerequisites, Platform Support, and Output

MethodPrerequisitesWorks Without WinRM?Offline Machine HandlingPlatform SupportStructured Output?
Invoke-CommandWinRM enabled on all targetsNoErrors unless Test-Connection pre-check usedWindows + macOS/Linux (SSH, PS 7+)Yes — PSComputerName on returned objects
PsExecTCP/445, UDP/137 open; Sysinternals downloadYesNo — errors or hangs on unreachable hostsWindows onlyNo — stdout/stderr only
Group PolicyActive Directory domain; GPO admin rightsYesSilent skip — runs on next refresh after machine comes back onlineWindows only (domain-joined)No — requires custom log step
Invoke-Command + SSHPowerShell 7+; SSH installed on targetsNo (uses SSH port 22)Same as WinRM path — pre-check neededWindows, macOS, LinuxYes — same PSComputerName objects
WMI (Invoke-CimMethod)DCOM ports; local admin on targetYesNo — errors on unreachable hostsWindows onlyYes — method return values
GPO Immediate Scheduled TaskActive Directory; more complex GPO configYesRuns on next refresh after machine reconnectsWindows only (domain-joined)No — requires script-side logging

Workgroup Machines, Remote Workers, and Mac Endpoints

This section assumes your fleet includes non-domain machines or Mac endpoints. If every machine is domain-joined and Windows-only, the Methods section above covers everything you need.

Workgroup Environments (No Active Directory)

Without a domain, WinRM requires three manual steps on every target machine before Invoke-Command will connect. First, add the target's IP to the admin machine's TrustedHosts list:

Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.1.50"

Second, always provide a -Credential parameter on every Invoke-Command call. Third, use HTTPS transport (port 5986) or TrustedHosts with NTLM authentication. If any of these steps are missing, you get the following error:

"If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting."

Scaling this across 20+ machines means configuring TrustedHosts on the admin machine and enabling WinRM individually on each target — which usually requires physical or RDP access first. For fleets over about 15 machines without AD, this manual bootstrapping becomes impractical quickly. At that scale, an RMM agent handles initial enrollment and keeps the management channel open without any per-machine WinRM configuration.

The r/sysadmin community's top recommendation for no-AD environments with 45+ endpoints is consistent across multiple threads: invest in an MDM or RMM rather than maintaining workgroup PowerShell remoting at scale.

Mac Endpoints and Cross-Platform SSH Remoting

SSH remoting was introduced in PowerShell 6.0 via the -HostName and -SSHConnection parameters. As of PowerShell 7.5 (GA, February 2025), SSH remoting is fully stable. Yes — you can run the same script against Windows and Mac targets in a single Invoke-Command call:

$sshConnections = @(
    @{ HostName = "WinServer1"; UserName = "Domain\UserA"; KeyFilePath = "C:\Users\UserA\id_rsa" }
    @{ HostName = "UserB@MacBook5"; KeyFilePath = "/Users/UserB/id_rsa" }
)
$results = Invoke-Command -FilePath C:\Scripts\GetInfo.ps1 -SSHConnection $sshConnections

To enable SSH remoting on macOS, go to System Preferences, open Sharing, enable Remote Login, then add the PowerShell subsystem entry to /private/etc/ssh/sshd_config:

Subsystem powershell /usr/bin/pwsh -sshs -NoLogo -NoProfile

Or use Enable-SSHRemoting from the Microsoft.PowerShell.RemotingTools module for a one-command setup that handles the sshd_config entry automatically.

SSH remoting does not support named endpoints, WinRM-based quotas, JEA, or disconnect/reconnect features. Plan for this if those are in use on your Windows machines — the SSH path and the WinRM path are separate transports with different feature sets.

Installing Software on Multiple Computers via PowerShell

The powershell script to install software remotely on multiple computers has two rules that practitioners learn the hard way. First: never run an installer from a UNC path inside a remote session. Second: silent install or it hangs.

The UNC path failure is a double-hop problem. The remote session's security context cannot authenticate to a second network resource — the file share — so the installer reports "access denied" or "file does not exist" even when you have permissions and the file is there. The fix is a two-step pattern:

# Step 1: Copy installer to target via admin share (runs in local context — no double-hop)
$installer = "C:\Packages\setup.exe"
Copy-Item $installer "\\$computer\c$\Windows\Temp\installer.exe"

# Step 2: Run it from the local path inside the remote session
Invoke-Command -ComputerName $computer -ScriptBlock {
    Start-Process "C:\Windows\Temp\installer.exe" -ArgumentList "/silent" -Wait
}

The $using: scope modifier is required if you pass $installer into the script block. Without it, the variable is empty inside the remote scope.

Note the Copy-Item -ToSession limitation: it only works on a single session object and cannot target multiple sessions simultaneously. The admin-share copy method above (\\computer\c$\) scales to a loop without this restriction.

Silent install flags by installer type: /silent for Inno Setup, /qn for MSI, /S for NSIS. If the vendor doesn't document a silent flag, the installer will hang in the remote session with no error output and no timeout.

Troubleshooting: If the installer reports "access denied" or "file does not exist" but you know the file is there, the remote session is trying to access a UNC path. Copy the installer locally first using the admin share pattern above.

Add a pending reboot check at the end of the script block so you know which machines need a restart after installation:

$rebootPending = Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
[pscustomobject]@{ ComputerName = $env:COMPUTERNAME; Installed = $true; RebootRequired = $rebootPending }

How Trio MDM Helps You Run Scripts Across Your Fleet

Every free method documented in this article requires you to solve a transport problem before you can run anything. WinRM needs to be enabled first. PsExec needs SMB ports open and, in many organizations, security team sign-off. GPO needs a domain. None of them gives you execution results per machine without additional scripting on top.

Trio MDM's Command Center is built to run scripts across your fleet from a single console, without building a WinRM bootstrap process or maintaining per-machine transport configuration. The Trio Agent handles the communication layer.

You can run one script across selected devices using Command Templates: create a reusable script once in the Trio interface and deploy it to selected devices without touching each machine individually. Command Results display execution results for all commands in the Trio console — no extra scripting required to capture them.

Scheduled Commands let you set a specific date and time for future script execution. No cron job or Task Scheduler configuration needed on target machines. Post-Enrollment Commands run automatically when a device finishes enrollment — useful for initial configuration without any manual triggering.

Trio MDM's RMM platform keeps the management channel open through the Trio Agent, so the per-machine WinRM bootstrap problem the article documents is a non-issue. The agent supports Windows (PowerShell and Command Prompt), macOS (Bash), and Linux (Bash) — managed from one console. Mac and Linux scripts run in Bash; Windows scripts run in PowerShell or Command Prompt depending on the template.

This is the same category of solution the r/sysadmin community points to when workgroup PowerShell remoting hits its ceiling. If you want to see how it works in your environment, start your free trial and deploy your first script in minutes. Or book a demo to see the Command Center and result reporting in action.

Ready-to-use Templates

Must-have Template Toolkit for IT Admins

Explore All
Template Toolkit

Start your free trial

No credit card required
Full access to all features

Get Ahead of the Curve

Every organization today needs a solution to automate time-consuming tasks and strengthen security. Without the right tools, manual processes drain resources and leave gaps in protection. Trio MDM is designed to solve this problem, automating key tasks, boosting security, and ensuring compliance with ease.

Don't let inefficiencies hold you back.

Every organization today needs a solution to automate time-consuming tasks and strengthen security. Without the right tools, manual processes drain resources and leave gaps in protection. Trio MDM is designed to solve this problem, automating key tasks, boosting security, and ensuring compliance with ease.

Smiling womanAbstract geometric patternAbstract geometric patternSmiling womanSmiling woman

Frequently Asked Questions

It queues. Once the first batch of 32 connections complete, the next batch starts automatically. No machines are failed or skipped. To run all targets simultaneously rather than in batches, set `-ThrottleLimit` to the total number of machines — for example, `-ThrottleLimit 100` for a 100-machine list. One note: a community commenter has circulated the default as 25, but the correct value per Microsoft Learn documentation is 32.

Yes — use `Invoke-Command -SSHConnection` with a hashtable array that mixes Windows and macOS targets in the same call. PowerShell 7+ is required, and SSH must be installed and configured on all target machines. SSH remoting does not support named endpoints, WinRM-based quotas, or JEA, so plan for those gaps if those features are in use on your Windows machines.

Yes. PsExec drops `PSEXESVC.exe` on the target machine at every invocation and removes it only on clean completion. If you interrupt the command, the service stub persists on the remote machine until the next successful PsExec run to that machine or until you clean it up manually. Security tools may flag this artifact as an indicator of lateral movement.

Yes, and it's a documented behavioral difference. When targeting a single machine, a terminating error in the remote script block propagates as terminating on the local machine, breaking try/catch handling. When targeting multiple machines with the same error, it correctly behaves as non-terminating. Test your error-handling logic against a multi-machine array, not a single machine. GitHub Issue #25087 (filed February 2025) tracks this behavior discrepancy — verify the current status before relying on this behavior in production code.

You need four things: WinRM enabled on each target (run `Enable-PSRemoting -Force` locally or bootstrap with PsExec); each target's IP or hostname added to `TrustedHosts` on the admin machine; a unique, non-blank password set on every target (blank passwords block NTLM authentication); and a `-Credential` parameter on every `Invoke-Command` call. For fleets larger than about 15 machines, this per-machine setup cost makes an MDM or RMM worth evaluating as the more practical path.
How to Run a PowerShell Script on Multiple Computers