Misc

Setting file permissions on a remote machine with PowerShell

Recently I needed to set some file permissions on a remote machine. Previously I’d done this relatively easily through a share as the user account I was using also had administrator rights on the other side and I was dealing with domain accounts. However, this did not work for a user that was local to the remote machine.

So, I creates a small PowerShell function to remotely set the user to a local (or any domain) account. (This also works for virtual accounts like IIS AppPool/ users)

function Add-RemoteAcl
(
    [string]$computerName,
    [string]$directory,
    [string]$user,
    [string]$permission
)
{
    $session = New-PSSession -ComputerName $computerName;
    Invoke-Command -Session $session -Args $directory, $user, $permission -ScriptBlock {
        param([string]$directory,[string]$user,[string]$permission)
        $acl = Get-Acl $directory;
        $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($user, $permission, "ContainerInherit, ObjectInherit", "None", "Allow");
        if ($accessRule -eq $null){
            Throw "Unable to create the Access Rule giving $permission permission to $user on $directory";
        }
        $acl.AddAccessRule($accessRule)
        Set-Acl -aclobject $acl $directory
    };
    Remove-PSSession $session;
}

To run the PowerShell remotely, first of all, I create a new PowerShell session on the remote machine with New-PSSession, then I run a script in that session with Invoke-Command, and finally I clean up with Remove-PSSession to end the remote session.

Bear in mind that you will need the appropriate permissions on the remote machine for whatever actions you want to take.

Invoke-Command

This is where all the work is done. You can pass a session to Invoke-Command, and you can also pass an ArgumentList to pass in to the command. This gives it some fantastic abilities.

Be aware that variables that exist outside the script block are not visible within the script block, you have to pass them as an ArgumentList (alias Args), and the script block has to pick them up. Hence the code above starts the script block with a params section in order to pick up the values passed as the Args.

Setting the file permissions

In order to add new rules to an ACL you have to Get-Acl to get the existing set of rules, create the new FileSystemAccessRule for the permission you want to grant, then AddAccessRule to the ACL you retrieved, and finally Set-Acl to persist the addition.

If you were just to create the new rule and set that, then all the existing rules would be replaced with the one rule that was just created.

Misc

IIS Administration file access

If you are using the IIS Administration ReST API to manage IIS, one thing that is not immediately obvious is that if you put your websites outside of %systemdrive%\inetpub you won’t be able to access them through the API. e.g. You won’t be able to set the physical path of a website to a location outwith %systemdrive%\inetpub.

If you do try to set the file outwith the default location, then you will get an 403 error from the API with a JSON response that looks like this:

{
    "title":"Forbidden",
    "name":"physical_path",
    "detail":"C:\\www\\MyWebSite",
    "status":403
}

So, you need to update its settings file (in my case, located at C:\Program Files\IIS Administration\2.2.0\Microsoft.IIS.Administration\config\appsettings.json) to include a files section. The files section is at the same level as security, logging, cors, etc.

e.g.

  "files": {
    "locations": [
      {
        "alias": "www",
        "path": "c:\\www",
        "claims": [
          "read"
        ]
      }
    ]
  }

This will allow websites/web-applications to be located in C:\www

Remember to restart the “Microsoft IIS Administration” Service after making changes to the appsettings.json file so that it will be picked up.

Restarting Microsoft IIS Administration Service
Restart Microsoft IIS Administration Service

You can also check which files IIS Administration has access to through the API. The end-point is /api/files/ and, if there are no files set up it will show an empty JSON array for the files part of the result.

API Result showing empty files section
API Result showing empty files section

Once the files section is added to the appsettings.json file and the Microsoft IIS Administration service is restarted, the API will show which files the API can access.

Populated file section in the IIS Administration API
Populated file section in the IIS Administration API

Finally, if you are having difficulty saving the appsettings.json file, read how to take ownership of a file in order to be able to be able to write to it.

Updates

Updated 14/March/2018: Note to restart the Windows Service; Show what files are available through the API; formatting.

Software Development

Custom routing to support multi-tenancy applications

The company I currently work for has many brands so they are looking for a website that can be restyled for each brand. They also want the styling information to be managed through an admin area rather than have to go to the development team each time they want to change something.

To that end I have added some custom routing into the application to allow assets to be delivered via a controller yet have the URL look like a path to a file on disk.

The summary of the steps involved are:

  • Set up a custom route with a custom route handler
  • Build the logic in the custom route handler to ensure that the data is passed to the controller correctly.
  • Update the web.config file to tell IIS to allow certain paths through to ASP.NET MVC that would otherwise look like a static path.

Setting up the custom route

My custom route is inside an area to keep all tenant specific URLs separate from the rest of the application.

public override void RegisterArea(AreaRegistrationContext context) 
{
    context.MapRoute(
        "Tenant_customLogic",
        "Tenant/{tenant}/content/{*contents}",
        new { action = "Index", controller="Content"}
    ).RouteHandler = new TenantRouteHandler();
}

So, this means that the routing engine can extract the “tenant” from the URL and it will also allow multiple path parts in the “contents” value. So, if the URL is: http://example.com/Tenant/MyBrand/content/my/virtual/file/path/to/styles.css then the RouteValueDictionary will contain:

  • tenant: MyBrand
  • controller = Content
  • action = Index
  • contents = my/virtual/file/path/to/styles.css

However, we want to split up the contents into its individual components, so a TenantRouteHandler class is created to do that.

Build the custom route handler

Without going too much in to what this class does in this specific instance (which isn’t relevant to the general concept) the basics are

  • Create a class that implements IRouteHandler
  • In GetHttpHandler process the routing information to get what we want in a format that suits the application.
  • Create a regular IRouteHandler object (normally an MvcRouteHandler) and call its GetHttpHandler() method with the updated requestContext as I otherwise want the same functionality as a regular handler.
public class TenantRouteHandler : IRouteHandler
{
    private readonly Func<IRouteHandler> _routeHandlerFactory;
 
    public TenantRouteHandler()
    {
        _routeHandlerFactory = ()=> new MvcRouteHandler();
    }

    public TenantRouteHandler(Func<IRouteHandler> routeHandlerFactory)
    {
        _routeHandlerFactory = routeHandlerFactory;
    }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        ProcessRoute(requestContext); // does stuff to modify the request context
        IRouteHandler handler = _routeHandlerFactory();
        return handler.GetHttpHandler(requestContext);
    }
}

ProcessRoute(requestContext) is the specific implementation that extracts the information out of the route and I then add it back into the RouteValueDictionary so my controller can access it. It isn’t relevant so I’m not including it.

What I also do here (because ASP.NET MVC, despite being launched in 2009 to a fanfare of being easy to unit test, isn’t east to unit test at all) is also set up some functional hooks that I can use in unit testing. The default constructor simply sets up the strategy to return a regular MvcRouteHandler and that’s the constructor that will be used in production. The other constructor is used in unit tests so that I can inject my own handler that doesn’t need tons of MVC infrastructure to be set up in advance.

So, the contents part of the route is now split out as we need it for the application.

Getting IIS to pass through these routes to ASP.NET MVC

Running the application at this point won’t return anything. In fact, IIS will throw up an error page that it cannot find the file. The expected paths all look like static content, which IIS thinks it is best placed to deal with. However, the web.config can be updated to let IIS know that certain paths have to be passed through to ASP.NET MVC for processing.

Once the following is added to the web.config everything should work as expected.

  <system.webServer>
    <handlers>
      <!-- This is required for the multi-tenancy part so it can serve virtual files that don't exist on the disk -->
      <add
        name="TenantVirtualFiles"
        path="Tenant/*"
        verb="GET"
        type="System.Web.Handlers.TransferRequestHandler"
        preCondition="integratedMode" />
    </handlers>
  </system.webServer>

And that’s it. Everything should work now. Anything in the Tenant/* path will be processed by ASP.NET MVC and the controller will decide what to serve up to the browser.

Software Development

Running an ASP.NET MVC application on a fresh IIS8 install

IIS has ever increasing amounts of security, you can’t publish a basic ASP.NET MVC website anymore and expect IIS to host it without some additional work. The default config settings that the MVC uses are locked down in IIS, so it issues an error when you try to navigate to your fresh site.

Initially you may get a screen that says something bland and non-descriptive, like “Internal Server Error” with no further information.

To get the more detailed error messages modify your web application’s web.config file and add the following line to the system.webServer section:

<httpErrors errorMode="Detailed" />

Now, you’ll get a more detailed error message. It will look something like this:

The key to the message is: This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault="Deny"), or set explicitly by a location tag with overrideMode="Deny" or the legacy allowOverride="false".

The “Config Source” section of the error message will highlight in red the part that is denied.

In order to allow the web.config to modify the the identified configuration element you need to find and modify the ApplicationHost.config file. It is located in C:\Windows\System32\inetsrv\config. You’ll need to be running as an Administrator level user in order to modify the file.

Find the section group the setting belongs to, e.g.

<sectionGroup name="system.webServer">

Then the section itself:

<section name="handlers" overrideModeDefault="Deny" />

And update overrideModeDefault to "Allow" in order to allow the web.config to override it.

When you refresh the page for the website the error will be gone (or replaced with an error for the next section that you are not permitted to override)

Software Development

Authenticating Across Virtual Directories

If you have an application set up in a way similar to the previous post, which is essentially a domain that contains a number of web application hosted in various virtual directories on the server.

In my previous example, the root of the domain contains the application that contains the account management (the sign in, password retrieval, account set up, etc.), however each of the applications in each virtual directory must know who is logged in.

Assuming you are using the .NET’s built in authentication mechanisms this is unlikely to work out of the box. There is some configuration that need to happen to allow each of the applications to sync up.

Setting up the web.config file

In MVC 4 Forms Authentication must be set up explicitly.

<system.web>
  <authentication mode="Forms">
  </authentication>
  <!-- Other config settings -->
</system.web>

To ensure that each application can decrypt the authentication ticket in the cookie, they all must share the same machine key as by default IIS will assign each application its own encryption and decryption keys for security.

<system.web>
  <machineKey decryptionKey="10FE3824EFDA35A7EE5E759651D2790747CEB6692467A57D" validationKey="E262707B8742B1772595A963EDF00BB0E32A7FACA7835EBE983A275A5307DEDBBB759B8B3D45CA44DA948A51E68B99195F9405780F8D80EE9C6AB46B9FEAB876" />
  <!-- Other config settings -->
</system.web>

Do not use the above key – it is only an example.

These two settings must be shared across each of the applications sitting in the one domain.

Generating a Machine Key

To generate a machine key:

  • Open “Internet Information Services (IIS) Manager” on your development machine.
  • Set up a dummy application so that it won’t affect anything else on the machine.
  • Open up the Machine Key feature in the ASP.NET section

    IIS Manager
    IIS Manager
  • (1) In the “Validation key” section uncheck “Automatically generate at runtime” and “Generate a unique key for each application”.

    Machine Key Configuration in the IIS Manager
    Machine Key Configuration in the IIS Manager
  • (2) In the “Decryption key” section uncheck “Automatically generate at runtime” and “Generate a unique key for each application”.
  • (3) Click “Generate Keys” (this will change the keys randomly each time it is pressed)
  • (4) Click “Apply”

The web.config for this web application will now contain the newly generated machine key in the system.web section. Copy the complete machineKey element to the applications that are linked together.

There is an “Explore” link on the site’s main page in IIS to open up Windows Exporer on the folder which contains the web site and the web.config file.

Software Development

RavenDB on IIS: Cannot access file, the file is locked or in use

I came across another issue with trying to get RavenDB working through IIS. When the process started up I got the error message “Cannot access file, the file is locked or in use”.

The stack trace looked like this:

[EsentFileAccessDeniedException: Cannot access file, the file is locked or in use]
   Microsoft.Isam.Esent.Interop.Api.Check(Int32 err) in C:\Work\ravendb\SharedLibs\Sources\managedesent-61618\EsentInterop\Api.cs:2736
   Raven.Storage.Esent.TransactionalStorage.Initialize(IUuidGenerator uuidGenerator) in c:\Builds\RavenDB-Stable\Raven.Storage.Esent\TransactionalStorage.cs:205

[InvalidOperationException: Could not open transactional storage: C:\inetpub\ravendb\Data\Data]
   Raven.Storage.Esent.TransactionalStorage.Initialize(IUuidGenerator uuidGenerator) in c:\Builds\RavenDB-Stable\Raven.Storage.Esent\TransactionalStorage.cs:220
   Raven.Database.DocumentDatabase..ctor(InMemoryRavenConfiguration configuration) in c:\Builds\RavenDB-Stable\Raven.Database\DocumentDatabase.cs:185
   Raven.Web.ForwardToRavenRespondersFactory.Init() in c:\Builds\RavenDB-Stable\Raven.Web\ForwardToRavenRespondersFactory.cs:84
   Raven.Web.RavenDbStartupAndShutdownModule.b__0(Object sender, EventArgs args) in c:\Builds\RavenDB-Stable\Raven.Web\BootStrapper.cs:13
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +270M

It turns out that I hadn’t given the account IIS was using access to the directory… Or rather, I had given access, I just didn’t tell IIS about the account so it was using the default account.

To fix the issue:

  • Go into the Application Pools section of IIS.
  • Click the Application pool used by the RavenDB site

  • Press “Advanced Settings…” on the right side of the dialog.
  • Ensure the correct account is set up for the “Identity” in the “Process Model” section.

  • Once you’ve set up the correct user, press "OK" for each of the dialogs and everything should be ready.
Software Development

Getting RavenDB working on IIS – 500.19

While trying to get RavenDB working in IIS I ran into a problem. I got the following error from IIS

HTTP Error 500.19 – Internal Server Error

The requested page cannot be accessed because the related configuration data for the page is invalid.

Module IIS Web Core
Notification BeginRequest
Handler Not yet determined
Error Code 0x80070021
Config Error This configuration section cannot be used at this path. This happens when the section is locked at a parent level. Locking is either by default (overrideModeDefault="Deny"), or set explicitly by a location tag with overrideMode="Deny" or the legacy allowOverride="false".
Config File \\?\C:\inetpub\ravendb\web.config

Config Source

    6: 	<system.webServer>
    7: 		<handlers>
    8: 			<add name="All" path="*" verb="*" type="Raven.Web.ForwardToRavenRespondersFactory, Raven.Web"/>

I’ve ignored some of the less interesting parts of the error message.

This can be fixed in IIS itself.

  • Open up IIS, click on the top node of the tree on the left side (the one labelled with the machine name)
  • Then double-click on the item in the centre pane marked “Feature Delegation”

  • Then find the entry marked “Handler Mappings” and set the delegation to “Read/Write” using the action links on the right side of the dialog.

When this is done the error moves on to the next part of the web.config

Config Source

    9: 		</handlers>
   10: 		<modules runAllManagedModulesForAllRequests="true">
   11: 			<remove name="WebDAVModule" />

At this point you have to also set the “Modules” to “Read/Write” also

Once that was set, I was good to go.