PowerShell is a powerful automation tool, but it can sometimes behave unexpectedly, particularly when running scripts or connecting to remote machines. This article explains common challenges and their solutions, focusing on recent PowerShell versions, including PowerShell 7.x on Windows Server 2022 and Windows 11. We will cover:
- PowerShell Execution Policy: How script execution is managed and why loosening it isn’t always the best approach.
- Remote Administration with PowerShell: Connecting to machines in different domains or workgroups.
- The Get-VM Cmdlet for Hyper-V: How it functions differently between Windows PowerShell and PowerShell 7.
- Get-ChildItem Output Quirks: Understanding why empty folders might not appear in results.
- Copy-Item and Move-Item with Wildcards: Surprising behavior when using wildcards, especially over network paths.
PowerShell Execution Policy
PowerShell’s execution policy determines which scripts are allowed to run. On modern Windows systems, including Windows 11 and Server 2022, the default policy is RemoteSigned. This setting allows locally created scripts to run freely, but scripts downloaded from the internet must be digitally signed or explicitly unblocked. Previously, older Windows versions defaulted to Restricted, allowing no scripts, which often confused new users. For non-Windows platforms like macOS and Linux, the default policy is effectively Unrestricted, which behaves similarly to the Bypass policy.
You can check the current policy using:
PowerShell
Get-ExecutionPolicy
If it returns Restricted, no scripts will run. Attempting to execute a script under this policy results in an error like:
File C:\Scripts\MyScript.ps1 cannot be loaded. The execution of scripts is disabled on this system...
This is expected behavior if the policy is too strict. Use Get-ExecutionPolicy -List to see all policy scopes and identify if a Group Policy or user-specific setting is blocking scripts.
PowerShell supports several common policies. See the Microsoft Docs for complete details, but the main ones are:
- Restricted: No scripts allowed; only interactive commands function.
- AllSigned: Only scripts signed by a trusted publisher can run.
- RemoteSigned: The default on Windows. Local scripts run, but downloaded scripts need signing or unblocking.
- Unrestricted: All scripts can run. PowerShell warns before executing unsigned scripts from the internet.
- Bypass: Nothing is blocked, and no warnings or prompts appear.
Note that default values can differ by environment. For instance, if all policy scopes are unset, the effective policy becomes Restricted on Windows clients but RemoteSigned on Windows servers.
Changing the Execution Policy (Use Caution)
If you must change the policy, use Set-ExecutionPolicy carefully, as overly permissive settings can expose your system to malicious scripts. For example, to allow all local scripts, run the following command as Administrator:
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
This sets the machine-wide policy to RemoteSigned. You need administrative rights to modify the LocalMachine scope; otherwise, you’ll encounter an “access denied” error.
For an even less restrictive, and therefore riskier, setting, you can use Unrestricted or Bypass. Unrestricted still provides warnings for internet files, whereas Bypass offers no warnings. On current Windows client operating systems, RemoteSigned is generally sufficient for most administrative tasks.
It’s important to note that on non-Windows platforms, execution policies cannot be strictly enforced; they are effectively unrestricted, and Set-ExecutionPolicy commands serve only informational purposes.
Using Execution Policy Flags for Single Sessions
If you prefer not to change the system policy permanently, you can bypass it for a single session. Launch PowerShell with the -ExecutionPolicy flag:
powershell.exe -ExecutionPolicy Bypass -File “C:\Scripts\MyScript.ps1”
Or, for PowerShell 7+, use the pwsh command:
PowerShell
pwsh -ExecutionPolicy Bypass -File "C:\Scripts\MyScript.ps1"
This starts PowerShell with the Bypass policy just for that specific process, allowing the script to run without altering the system’s default policy.
Alternatively, within an interactive session, you can temporarily set the policy for that session only:
Set-ExecutionPolicy -Scope Process Bypass
This change affects only the current session — a preference stored in the $Env:PSExecutionPolicyPreference environment variable — and is discarded when you close PowerShell. Use these methods for one-off script executions without changing the default security posture.
Orchestrating Remote Computers with PowerShell
PowerShell remoting enables running commands on other machines. For computers joined to the same domain, Kerberos authentication works seamlessly. You can use commands like Enter-PSSession -ComputerName Server01 -Credential (Get-Credential) or Invoke-Command. However, if the target machine is not in your domain, such as one in a workgroup or a different domain, the default Kerberos trust mechanism fails.
In such scenarios, you have several alternatives:
1. TrustedHosts (using WSMan): A common approach involves using WinRM over HTTP. You add the remote host to your TrustedHosts list on the local machine or client. Run this command, replacing TargetHostName:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "TargetHostName" -Concatenate
To trust any host, which is not broadly recommended for security reasons, use:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force
This configures WinRM to allow connections to the specified host via HTTP without full trust. Enter-PSSession -ComputerName TargetHostName -Credential (Get-Credential) should now work. Ensure WinRM is enabled on the remote machine by running Enable-PSRemoting or winrm quickconfig. Microsoft documentation highlights that adding the server to TrustedHosts is mandatory for workgroup connections.
2. HTTPS with Certificates: You can configure WinRM to use HTTPS if the server possesses a valid SSL certificate. Remoting using the -UseSSL parameter will then encrypt traffic and doesn’t require TrustedHosts configuration. This method is more secure but involves setting up certificates, a process beyond the scope of this article.
3. SSH Transport (PowerShell 7 and later): PowerShell 7 introduced SSH-based remoting. If you install an OpenSSH server on the remote Windows machine, or use SSH already available on Linux or Mac, you can connect using:
Enter-PSSession -HostName user@remotehost
or
New-PSSession -HostName user@remotehost
This method uses SSH for authentication with the user’s SSH credentials, bypassing Kerberos or WSMan settings. As Microsoft notes, PowerShell remoting over SSH relies entirely on the SSH client-server authentication exchange and implements no proprietary schemes. For Windows 11 or Server 2022, install the OpenSSH server feature to use this alternative, completely avoiding domain trust issues.
The Get-VM Cmdlet for Hyper-V
The Get-VM cmdlet is part of the Hyper-V PowerShell module, Microsoft.HyperV.Management. Its availability depends on whether the Hyper-V components are installed. If Hyper-V isn't present, the cmdlet won't exist or won't return results.
Running Get-VM usually requires Administrator privileges or membership in the Hyper-V Administrators group; otherwise, you might see no output or receive an error.
In PowerShell 7.x on Windows, the Hyper-V module works natively if the feature is enabled. You might need to install the Hyper-V management tools or RSAT for Hyper-V. On a Windows 11 client, enable it via Optional Features:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell
On Windows Server:
Install-WindowsFeature -Name Hyper-V-PowerShell
Once enabled, simply run Import-Module Hyper-V (or just execute Get-VM) in PowerShell 7. Microsoft’s compatibility list confirms the Hyper-V module is natively compatible with PowerShell 7 on modern Windows versions (Windows 10/Server 2019 and later) with the feature installed. Remember to run PowerShell as Administrator to ensure you can see the VMs.
Get-ChildItem Output Quirks
Get-ChildItem, also known by aliases dir or ls, lists directory contents. When using the -Recurse parameter, it lists all subfolders and files recursively. A potentially surprising behavior is that empty directories do not appear in the output. As the official documentation notes, Get-ChildItem doesn’t display empty directories when using -Recurse. This means if a folder has no files or subfolders within it, it simply won’t be listed; no error occurs, it just returns nothing for that specific folder.
You can leverage this behavior. For instance, consider C:\Test containing two subfolders: DirA (with files) and DirB (empty). Running:
Get-ChildItem -Path C:\Test -Recurse
will show both DirA and DirB under C:\Test. However, if you run:
Get-ChildItem -Path C:\Test\* -Recurse
Note the \* wildcard. This tells PowerShell to expand all children of C:\Test before recursing. In this case, DirB will not appear in the output at all because it contains nothing. Appending \* essentially hides empty directories because they produce no output, making it seem as if they aren’t there. This quirk applies across PowerShell versions and can be useful when you need to ignore empty folders in scripts.
Copy-Item and Move-Item (Wildcards on Remote Paths)
Copy-Item and Move-Item are used for file and folder operations. A common pitfall involves how wildcards behave when paths don’t exist. For example, if you attempt:
Move-Item \\RemoteServer\Share\* D:\Local\Dest
and \\RemoteServer\Share does not exist, perhaps due to a typo, you might expect an error. Without the wildcard, PowerShell would report: Move-Item : Cannot find path ‘\\RemoteServer\Share’ because it does not exist. However, with the \* included, PowerShell interprets this as “no items matched the wildcard” and silently completes without doing anything and showing no error. Testing confirms this behavior; the command appears to succeed, but no files are actually moved.
This means that typos or inaccessible shares can be difficult to detect when using wildcards. Always verify the outcome of copy or move operations. A good practice is to pipe results to another command or check the path using Test-Path beforehand. Also, remember that when these cmdlets run in a remote PowerShell session, paths refer to the remote machine’s filesystem. To manage local versus remote paths explicitly, consider using the -ToSession or -FromSession parameters available since PowerShell 5.1, or PowerShell 7’s implicit remoting features.
Conclusion
This article addressed several common scenarios that cause PowerShell commands or scripts to fail unexpectedly. Key takeaways include understanding the execution policy and how to bypass it safely when necessary; configuring remoting correctly using methods like TrustedHosts or SSH when dealing with non-domain-joined machines; ensuring required modules, such as the Hyper-V module, are installed and imported, especially in PowerShell 7; and being aware of subtle behaviors like Get-ChildItem -Recurse skipping empty folders or wildcards potentially suppressing errors in Copy-Item and Move-Item. Hopefully, these explanations clarify these issues and contribute to a smoother PowerShell experience. Happy scripting!