On this page
- Installing with winget
- Finding and installing a package
- Listing, upgrading, and removing
- The Internet Explorer prompt that blocks downloads
- Use winget or PowerShell instead
- Copy the file from your own machine
- Turning IE ESC off, properly
- Installing common runtimes
- Windows roles and features
- Running an application as a service
- Why it stops
- Check whether the software installs its own service
- Registering an existing service binary
- Using a startup task instead
- Recovery for a real service
- Prove it with a reboot
- Let the traffic in
- Related
Installing software on Windows Server differs from a desktop in two ways that catch people out: the browser refuses to download anything until you deal with a security setting, and an application you start by hand dies when you sign out. This page covers both, plus winget and the runtimes most software depends on.
Installing with winget
winget is Microsoft's package manager. It installs from a command line, which
means an install is one repeatable line rather than a browser session and a
wizard.
Check whether you have it:
winget --versionIt ships with Windows Server 2025 and with Windows 11. On Windows Server 2019
and 2022 it is not installed by default. To add it on a server with Desktop
Experience, download the App Installer package, Microsoft.DesktopAppInstaller,
from Microsoft's WinGet releases and install it with Add-AppxPackage, along
with its Microsoft.VCLibs and Microsoft.UI.Xaml dependencies. On Server Core
there is no Appx support, so use the vendor's own installer instead.
Finding and installing a package
winget search "7-zip"Take the value in the Id column, and install it by that rather than by name,
because names collide and ids do not:
winget install --id 7zip.7zip --exactOn a server you usually want the machine-wide copy and no prompts:
winget install --id 7zip.7zip --exact --scope machine --silent --accept-package-agreements --accept-source-agreements--scope machine installs for every account rather than only the one you are
signed in as, which matters when the software will later run as a service under
a different account. --silent runs the underlying installer without its
wizard. The two --accept switches skip the licence and source prompts, which
otherwise stop an unattended run on the first invocation.
Listing, upgrading, and removing
winget list
winget upgrade
winget upgrade --all --include-unknown
winget uninstall --id 7zip.7zipwinget upgrade on its own shows what has a newer version. --include-unknown
adds packages whose installed version winget cannot read, which is common for
software that was installed before winget was.
The Internet Explorer prompt that blocks downloads
Open a browser on a fresh Windows Server, go to a download page, and you get a dialog headed Internet Explorer Enhanced Security Configuration is enabled telling you content from the site is blocked. Every site is untrusted, and you can add them to the trusted list one at a time forever without getting anywhere.
That setting, usually shortened to IE ESC, is on by default on Windows Server and it is on for a reason. Browsing the web from a server, signed in as an administrator, is how a server gets compromised by its owner. So the first two answers below are better than the third.
Use winget or PowerShell instead
If the software is in winget, install it with winget and the question does not arise. If it is not, fetch the installer directly:
Invoke-WebRequest -Uri "https://example.com/app.msi" -OutFile "C:\Temp\app.msi" -UseBasicParsing-UseBasicParsing matters on Windows PowerShell 5.1. Without it, the command
tries to use the Internet Explorer engine to parse the response and fails with a
message about Internet Explorer's first-launch configuration not being complete,
which is the same underlying setting wearing a different hat. On PowerShell 7
the switch is unnecessary and harmless.
Downloads arrive marked as coming from the internet, which makes some installers refuse to run. Clear the mark, and check what you fetched is what you meant to fetch, before running it:
Get-FileHash "C:\Temp\app.msi" -Algorithm SHA256
Unblock-File -Path "C:\Temp\app.msi"
msiexec /i "C:\Temp\app.msi" /qn /norestart/qn runs the MSI with no interface and /norestart stops it rebooting the
server underneath you.
Copy the file from your own machine
Remote Desktop can share a local drive with the server, which is often the quickest route for something you already have. Before connecting, in Remote Desktop Connection open Show Options, then Local Resources, then More, and tick the drive you want. It appears in the server's File Explorer under Redirected drives and folders. The clipboard works for small files and for text in the same way.
Turning IE ESC off, properly
If you genuinely need a browser on the server, turn the setting off for administrators only, do the download, and turn it back on in the same sitting.
In Server Manager, open Local Server, find IE Enhanced Security Configuration, and set Administrators to Off. Leave Users on.
From PowerShell:
$admins = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
Set-ItemProperty -Path $admins -Name "IsInstalled" -Value 0Sign out and back in for it to take effect. Put it back with the same command
and -Value 1.
Installing common runtimes
Most software needs a runtime underneath it, and the failure when one is missing is rarely clear. A native application that closes instantly with a missing DLL message almost always wants the Visual C++ redistributables.
Package ids move as new major versions ship, so confirm with winget search
before using one. These are the ids for the common runtimes:
| Runtime | winget id |
|---|---|
| Visual C++ redistributables | Microsoft.VCRedist.2015+.x64 |
| .NET Desktop Runtime | Microsoft.DotNet.DesktopRuntime.8 |
| Node.js LTS | OpenJS.NodeJS.LTS |
| Python 3 | Python.Python.3.12 |
| Java, Eclipse Temurin | EclipseAdoptium.Temurin.21.JDK |
| Git | Git.Git |
| 7-Zip | 7zip.7zip |
Install the Visual C++ redistributables on any server that will run game servers, native applications, or anything distributed as a compiled binary. It costs nothing and it removes an entire class of confusing startup failure.
After installing a runtime, open a new PowerShell window before testing it. The
installer adds the runtime to the system PATH, and a window that was already
open keeps the old one, so node or python appears to be missing when it is
installed correctly.
Windows roles and features
Roles are not packages, and on Windows Server they have their own cmdlets:
Get-WindowsFeature | Where-Object { $_.InstallState -eq "Installed" }
Install-WindowsFeature -Name Web-Server -IncludeManagementTools-IncludeManagementTools is worth remembering. Without it you get IIS with no
IIS Manager, which is a confusing thing to discover later.
Some features, .NET Framework 3.5 in particular, are not on the disk at all
and need -Source pointed at the sxs folder of the matching Windows
installation media.
Running an application as a service
This is the thing people most often get wrong. You install a game server, a bot, or an API, start it in a PowerShell window, confirm it works, close Remote Desktop, and some hours later it is gone.
Why it stops
Two different things happen depending on how you leave the session.
Closing the Remote Desktop window with the X leaves the session disconnected. Everything you started keeps running, which is exactly why this looks like it works.
Choosing Sign out ends the session and terminates every process started in it. So does a reboot, and so does the session being reaped after it has sat disconnected for a while.
That is the whole trap: the way people test leaves the process running, and the way the server eventually behaves does not. A disconnected session is also not supervision. It does not restart the application if it crashes, and it does not start it after a reboot.
Anything that needs to stay up needs to be a service or a startup task.
Check whether the software installs its own service
Read its documentation first. Databases, web servers, and most serious server
software ship a service installer or an --install-service style flag, and
using it is better than anything you build yourself. Skip the rest of this
section if so.
Registering an existing service binary
If the executable is genuinely service-aware, register it:
New-Service -Name "myapp" -BinaryPathName "C:\apps\myapp.exe" -DisplayName "My App" -StartupType AutomaticThe older tool does the same, and note that a space after each = is required,
not optional:
sc.exe create myapp binPath= "C:\apps\myapp.exe" start= autoTo supervise a normal executable as a service you need a service wrapper such as NSSM or WinSW. Both are third-party tools you download and configure yourself, and neither is part of Windows.
Using a startup task instead
If you would rather not add a third-party wrapper, Task Scheduler is built in and covers the important parts: it starts at boot, before anyone signs in, it runs independently of any session, and it can restart the application if it exits.
$action = New-ScheduledTaskAction -Execute "C:\apps\myapp.exe" -WorkingDirectory "C:\apps"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Seconds 0)
Register-ScheduledTask -TaskName "myapp" -Action $action -Trigger $trigger -Principal $principal -Settings $settings-ExecutionTimeLimit (New-TimeSpan -Seconds 0) means no time limit, which you
need, because the default stops a long-running task after three days.
-RestartCount and -RestartInterval have to be given together.
Start it now rather than waiting for a reboot, and read back what happened:
Start-ScheduledTask -TaskName "myapp"
Get-ScheduledTaskInfo -TaskName "myapp"Three things about running as SYSTEM are worth knowing before you do:
- There is no window.
SYSTEMhas no desktop, so a console application's output goes nowhere. The application must write a log file, or you will have no idea what it is doing. - Use absolute paths everywhere, in the task and in the application's own
configuration.
SYSTEMhas a different profile directory and no mapped drives. SYSTEMis highly privileged. For anything exposed to the internet, create a dedicated local account with only the access it needs and register the task under that instead, which requires supplying that account's password when you register it.
Recovery for a real service
A registered service can restart itself after a crash, but not by default:
sc.exe failure myapp reset= 86400 actions= restart/60000/restart/60000/restart/60000
Set-Service -Name myapp -StartupType AutomaticThat restarts the service a minute after each of the first three failures, and resets the failure count after a day.
Prove it with a reboot
Whichever route you took, the only test that counts is a restart:
Restart-ComputerReconnect and confirm the application is running without you having started it:
Get-Service -Name myapp
Get-Process -Name myappIf a service failed to start, the reason is in the System event log:
Get-WinEvent -LogName System -MaxEvents 30 |
Where-Object { $_.ProviderName -eq "Service Control Manager" } |
Format-Table TimeCreated, Id, Message -WrapLet the traffic in
An application that is running is still unreachable until its port is open. Add the Windows Firewall rule with Using Windows Firewall, and add the matching Port rule in the panel so the port's rate limits and protocol checks apply to it.