PowerShell VirtualDirectory Wrappers

on Friday, October 17, 2014

The WebAdministration Module ships with a couple functions for working with virtual directories:

But, there are some glitches with them.

A way to get around these problems is to write a wrapper class around the functions. Making the PhysicalPath property on New-WebVirtualDirectory is pretty easy to do, but the other one …

To prevent the confirmation prompt from popping up, the wrapper function can create an empty temporary directory, repoint the virtual directory to it, remove the virtual directory, and then remove the temporary directory.

This code below also wrapped the Get-WebVirtualDirectory because I wanted an API that took a Url as input and figure out how to use it.

Here’s a full list of the wrappers and helper functions:

<#
.SYNOPSIS
 Takes a url and breaks it into these parts: Ssl, SiteName, AppName, AppNames.
 
.DESCRIPTION 
 Takes a url and breaks it into these parts for a [PSObject]:

 Ssl   - true/false - does the url request ssl
 SiteName - string - the dns host name
 AppName  - string - the AppNames as a single string. It starts with '/'.
 AppNames - Array<string> - each folder name within the local path

.PARAMETER Url
 The url to convert into it's UriPaths

.EXAMPLE
 $uriPaths = ConvertTo-WebUriPaths "https://www.contoso.com/services"
#>
Function ConvertTo-WebUriPaths {
Param (
 [Parameter(Mandatory = $true)]
 [string] $Url
)

 $paths = @{
  Ssl = $null;
  SiteName = "";
  AppNames = @();
  AppName = "";
 };

 $uri = New-Object System.Uri $Url;

 $paths = New-PsType "UriPaths"

 Add-PsTypeField $paths "Ssl" ($uri.Scheme -eq "https")
 Add-PsTypeField $paths "SiteName" $uri.Host
 Add-PsTypeField $paths "AppNames" $uri.LocalPath.Split("/", [StringSplitOptions]::RemoveEmptyEntries)
 Add-PsTypeField $paths "AppName" $uri.LocalPath

 # remove trailing slash, if exists
 if($paths.AppName) {
  $appNameLen = $paths.AppName.Length;
  if($paths.AppName[$appNameLen - 1] -eq "/") {
   $paths.AppName = $paths.AppName.Substring(0, $appNameLen - 1);
  }
 }

 return $paths;
}



<#
.SYNOPSIS
 Takes a UriPaths object (from ConvertTo-UriPaths) and turns it into
 and IIS:\Sites\XXXX string value.

.PARAMETER UriPaths
 The UriPaths object from ConvertTo-WebUriPaths

.EXAMPLE
    $uriPaths = ConvertTo-WebUriPaths "https://www.contoso.com/services"
 $iisPath = ConvertTo-WebIISPath $uriPaths

.LINK
 ConvertTo-WebUriPaths
#>
Function ConvertTo-WebIISPath {
Param (
 [Parameter(Mandatory = $true)]
 [PSObject] $UriPaths
)

 $iisPath = "IIS:\Sites\" + $UriPaths.SiteName;
 $UriPaths.AppNames |% { $iisPath += "\" + $_ }; # alternateively, AppName could also be used

 return $iisPath;
}



<#
.SYNOPSIS
    Using the given url to search the current server for the longest parent website/webapp path that matches the url.
    It will only return the parent website/app information. if the root website is given, then $null will be returned.
    The webapp's information from the IIS:\Sites protocol is returned.

.PARAMETER Url
    The url to search on
    
.EXAMPLE
    $webApp = Get-WebParentAppByUrl "http://www.contoso.com/services" 
#>
Function Get-WebParentAppByUrl {
Param (
    [Parameter(Mandatory = $true)]
    [string] $Url
)

    $uriPaths = ConvertTo-WebUriPaths -Url $Url

    $currentPath = "IIS:\Sites\{0}" -f $uriPaths.SiteName
    if((Test-Path $currentPath) -eq $false) { return $null }
    if($uriPaths.AppName -eq "" -or $uriPaths.AppName -eq "/") { return $null}

    $webApp = Get-Item $currentPath
    if($uriPaths.AppNames -is [Array]) {
        for($i = 0; $i -lt $uriPaths.AppNames.Count - 1; $i++) {
            $currentPath += "\{0}" -f $uriPaths.AppNames[$i]
            if(Test-Path $currentPath) { $webApp = Get-Item $currentPath }
        }
    }

    return $webApp
}



<#
.SYNOPSIS
 Get virtual directory information from a site/app. If the given Url is a site/app, this will
    search for all virtual directories at the same level as the given the path. If not a site/app,
    this will search for a virtual directory under the parent site/app.

.PARAMETER Url
 The url to search at.

.EXAMPLE
 $vdirs = Get-WebVirtualDirectoryWrapper -Url "http://www.contoso.com"
#>
Function Get-WebVirtualDirectoryWrapper {
[OutputType([Microsoft.IIs.PowerShell.Framework.ConfigurationElement])]
Param (
    [Parameter(Mandatory = $true)]
    [string] $Url
)

    $uriPaths = ConvertTo-WebUriPaths -Url $Url

    # check if the url is a site/app
    $iisPath = ConvertTo-WebIISPath -UriPaths $uriPaths

    if(-not (Test-Path $iisPath)) {
        Write-Warning "IIS $env:COMPUTERNAME - No path could be found for '$Url'. No virtual directories could be looked up."
        return $null
    }

    $node = Get-Item $iisPath
    if(@("Application", "Site") -contains $node.ElementTagName) {
        # search for virtual directories below this level

        $vdirs = Get-WebVirtualDirectory -Site $uriPaths.SiteName -Application $uriPaths.AppName

    } else {
        # search the parent app for the given virtual directory name

        $parentApp = Get-WebParentAppByUrl $Url
        $vdir = $uriPaths.AppName.Substring($parentApp.path.Length)
        $appPath = $parentApp.path
        if(-not $appPath) { $appPath = "/" }

        $vdirs = Get-WebVirtualDirectory -Site $uripaths.SiteName -Application $appPath -Name $vdir
    }
        
    return $vdirs
}


<#
.SYNOPSIS
 Set a virtual directory for a site/app. This will set the physical path for the given Url.

.PARAMETER Url
 The url to turn into a virtual directory.

.PARAMETER PhysicalPath
    The physical path on the server to attach to the virtual path.

.PARAMETER Force
    Overwrites the current physical path if already set.

.EXAMPLE
    # Create a new virtual directory

 $vdir = New-WebVirtualDirectoryWrapper `
                    -Url "http://admissions.{env}.sa.ucsb.edu" `
                    -PhysicalPath "D:\AllContent\Data\admissions.{env}.sa.ucsb.edu"
                    -ServerName "SA89"  
#>
Function New-WebVirtualDirectoryWrapper {
[OutputType([Microsoft.IIs.PowerShell.Framework.ConfigurationElement])]
Param (
    [Parameter(Mandatory = $true)]
    [string] $Url,
    [Parameter(Mandatory = $true)]
    [string] $PhysicalPath,
    [switch] $Force
)

    $uriPaths = ConvertTo-WebUriPaths -Url $Url

    # parse the name of the virtual directory from the given url
    if($uriPaths.AppName -eq "" -or $uriPaths.AppName -eq "/") {
        throw "IIS $env:COMPUTERNAME - No virtual path could be found in url '$Url'. A subpath needs to be defined within the url."
    }

    $parentApp = Get-WebParentAppByUrl -Url $Url

    if($parentApp -eq $null) {
        throw "IIS $env:COMPUTERNAME - No parent application could be found for url '$Url'. No virtual directory could be added."
    }

    $appPath = $parentApp.path
    if(-not $appPath) { $appPath = "/" }

    $vdirPath = $uriPaths.AppName.Substring($appPath.Length)

    # if the vdirPath is multiple levels deep, check that the root path exists
    if($vdirPath.Split("/", [StringSplitOptions]::RemoveEmptyEntries).Count -gt 1) {
        $i = $vdirPath.LastIndexOf("/")
        $rootSubLevel = $vdirPath.Substring(0,$i).Replace("/","\")
        $iisPath = "IIS:\Sites\{0}\{1}" -f $uriPaths.SiteName, $rootSubLevel
        if((Test-Path $iisPath) -eq $false) {
            throw "IIS $env:COMPUTERNAME - Part of the sub path for '$Url' could not be found. Please ensure the full base path exists in IIS."
        }
    }

    Write-Warning "IIS $env:COMPUTERNAME - Creating a virtual directory for $Url to $PhysicalPath."
    if($Force) {
        if($appPath -eq "/") { # it adds an extra / if you set the applicationName to '/'
            $vdir = New-WebVirtualDirectory -Site $uriPaths.SiteName -Name $vdirPath -PhysicalPath $PhysicalPath -Force
        } else {
            $vdir = New-WebVirtualDirectory -Site $uriPaths.SiteName -Application $appPath -Name $vdirPath -PhysicalPath $PhysicalPath -Force
        }
    } else {
        if($appPath -eq "/") { # it adds an extra / if you set the applicationName to '/'
            $vdir = New-WebVirtualDirectory -Site $uriPaths.SiteName -Name $vdirPath -PhysicalPath $PhysicalPath
        } else {
            $vdir = New-WebVirtualDirectory -Site $uriPaths.SiteName -Application $appPath -Name $vdirPath -PhysicalPath $PhysicalPath
        }
    }
    Write-Host "IIS $env:COMPUTERNAME - Created a virtual directory for $Url to $PhysicalPath."

    return $vdir
}


<#
.SYNOPSIS
 Removes a virtual directory from a site/app. It will only remove the virtual directory if the Url
    given matches up with a virtual directory.

.PARAMETER Url
 The url to search at.

.EXAMPLE
    Remove-WebVirtualDirectoryWrapper -Url "http://www.contoso.com/services"
#>
Function Remove-WebVirtualDirectoryWrapper {
Param (
    [Parameter(Mandatory = $true)]
    [string] $Url
)

    $uriPaths = ConvertTo-WebUriPaths -Url $Url

    # parse the name of the virtual directory from the given url
    if($uriPaths.AppName -eq "" -or $uriPaths.AppName -eq "/") {
        throw "IIS $env:COMPUTERNAME - No virtual path could be found in url '$Url'. A subpath needs to be defined within the url."
    }

    $parentApp = Get-WebParentAppByUrl -Url $Url

    if($parentApp -eq $null) {
        throw "IIS $env:COMPUTERNAME - No parent application could be found for url '$Url'. No virtual directory could be added."
    }

    # ensure the path is a virtual directory
    $iisPath = ConvertTo-WebIISPath -UriPaths $uriPaths
    if(-not (Test-Path $iisPath)) {
        throw "IIS $env:COMPUTERNAME - No path for $Url could be found in IIS."
    }

    $node = Get-Item $iisPath
    if($node.ElementTagName -ne "VirtualDirectory") {
        switch($node.GetType().FullName) {
            "System.IO.FileInfo" { $type = "File" }
            "System.IO.DirectoryInfo" { $type = "Directory" }
            "Microsoft.IIs.PowerShell.Framework.ConfigurationElement" { $type = $node.ElementTagName }
        }
        throw "IIS $env:COMPUTERNAME - The url '$Url' doesn't match with a Virtual Directory. It is a $type."
    }

    $vdirPath = $uriPaths.AppName.Substring($parentApp.path.Length)

    # check if the virtual path has files or folders beneath it. An error will occur if there are.
    $iisVPath = ConvertTo-WebIISPath -UriPaths $uriPaths

    $childItems = Get-ChildItem $iisVPath
    if($childItems) {
            Write-Warning ("IIS $env:COMPUTERNAME - The virtual path at '$Url' has items beneth it. Due to a bug in " + `
            " WebAdministration\Remove-WebVirtualDirectory this would force a windows pop-up dialog to get approval." + `
            " To get around this, a temporary folder will be created and the current virtual directory will" + `
            " be repointed to the new (empty) location before removal. After removal of the virtual directory" + `
            " the temporary folder will also be removed. The domain account this process runs under will need" + `
            " permissions to the temporary folder location to create and remove it.")


        $guid = [Guid]::NewGuid()
        $PhysicalPath = (Get-WebVirtualDirectoryWrapper -Url $Url).PhysicalPath
        $tempPath = Join-Path $PhysicalPath $guid

        Write-Warning "IIS $env:COMPUTERNAME - Creating temp directory '$tempDir' in order to remove a virtual directory."
        $tempDir = New-Item $tempPath -ItemType Directory
        Write-Host "IIS $env:COMPUTERNAME - Created temp directory '$tempDir' in order to remove a virtual directory."
        $void = New-WebVirtualDirectoryWrapper -Url $Url -PhysicalPath $tempPath -Force
    }

    $appPath = $parentApp.path
    if(-not $appPath) { $appPath = "/" }

    Write-Warning "IIS $env:COMPUTERNAME - Removing a virtual directory '$vdirPath' for '$Url'."
    Remove-WebVirtualDirectory -Site $uriPaths.SiteName -Application $appPath -Name $vdirPath
    Write-Host "IIS $env:COMPUTERNAME - Removed a virtual directory '$vdirPath' for '$Url'."

    if($tempDir) {
        Write-Warning "IIS $env:COMPUTERNAME - Removing temp directory '$tempDir' in order to remove a virtual directory."
        $void = Remove-Item $tempDir
        Write-Host "IIS $env:COMPUTERNAME - Removed temp directory '$tempDir' in order to remove a virtual directory."
    }
}

0 comments:

Post a Comment


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.