When hosting HTTP WCF services as a self-hosted Windows Services the server needs to have the HTTP Namespace reserved. The reservation allows for the domain account which runs the service to setup a listener on a particular port, for a particular address.
There are some tools already available which can help in this process:
- HTTP Namespace Manager – A nice GUI interface, which is easy to understand and setup. It also works on Server Core Servers.
- httpcfg – Windows Server 2003
- netsh – Windows Server 2008+
But, there are no PowerShell wrappers for these commands. So, here’s a wrapper that provides:
- Add-HttpNamespace
- Get-HttpNamespace
- Get-HttpNamespaces
- Test-HttpNamespaceExists
There’s no remove because I haven’t needed it yet. A namespace is usually associated with a particular port, and I haven’t been involved in a situation where a port needed to be reused.
<#
.SYNOPSIS
Parses the output from netsh to turn them in PSObjects.
#>
Function Get-HttpNamespaces {
[CmdletBinding()]
[OutputType([PSObject[]])]
Param()
# the $propsReady variable causes alot of errors to occur, but the results are accurate.
# so this helps hide the errors
$originalErrorAction = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
try {
# pull the data from netsh
$urlaclOutput = . netsh http show urlacl
# parse the data into PSObjects
$httpNamespaces = New-Object System.Collections.Generic.List[PSObject]
$props = @{}
$userProps = @{}
$userRdy = $false
for($i = 0; $i -lt $urlaclOutput.Count; $i++) {
$line = $urlaclOutput[$i].Trim()
$split = $line.Split(":", [StringSplitOptions]::RemoveEmptyEntries)
$first = ""
if($split.Count -gt 0) { $first = $split[0] }
# line parsing
switch($first.Trim()) {
"Reserved URL" {
$props.ReservedUrl = $line.Substring(25).Trim()
$users = New-Object System.Collections.Generic.List[PSObject]
}
"User" {
if($userRdy) {
$user = New-Object PSObject -Property $userProps
$users.Add($user)
$userProps = @{}
$userRdy = $false
}
$userProps.User = $split[1].Trim()
}
"Listen" { $userProps.Listen = $split[1].Trim() }
"Delegate" {
$userProps.Delegate = $split[1].Trim()
$userRdy = $true
}
"SDDL" {
$userProps.SDDL = $line.Substring(5).Trim()
$userRdy = $true
}
"" {
if($userRdy) {
# user
$user = New-Object PSObject -Property $userProps
$users.Add($user)
$userProps = @{}
# url
$props.Users = $users.ToArray()
$cnObj = New-Object PSObject -Property $props
$httpNamespaces.Add($cnObj)
$props = @{}
# reset flag
$userRdy = $false
}
}
}
}
} finally {
$ErrorActionPreference = $originalErrorAction # revert the error action
}
return $httpNamespaces.ToArray()
}
<#
.SYNOPSIS
Retrieves the namespace information for a given namespace. It will also search for namespaces
which match but the host names have been replaced with + or * symbols.
#>
Function Get-HttpNamespace {
[CmdletBinding()]
[OutputType([PSObject])]
Param (
[Parameter(Mandatory = $true)]
[string] $HttpNamespace
)
$httpNamespaces = Get-HttpNamespaces
# get * and + versions of the url ready
$starNamespace = $HttpNamespace
$plusNamespace = $HttpNamespace
$namespaceRegex = [regex] "http.*://(.*):.*/.*"
if($HttpNamespace -match $namespaceRegex) {
$hostname = $Matches[1]
$starNamespace = $HttpNamespace.Replace($hostname, "*")
$plusNamespace = $HttpNamespace.Replace($hostname, "+")
}
# sometimes the http namespaces get /'s added to the end
$namespace = $httpNamespaces |? {
$_.ReservedUrl -eq $HttpNamespace `
-or $_.ReservedUrl -eq ($HttpNamespace + '/') `
-or $_.ReservedUrl -eq $starNamespace `
-or $_.ReservedUrl -eq ($starNamespace + '/') `
-or $_.ReservedUrl -eq $plusNamespace `
-or $_.ReservedUrl -eq ($plusNamespace + '/')
}
return $namespace
}
<#
.SYNOPSIS
Checks if a namespace already exists. It will also search if the namespace has had its host name
replaced with + or * symbols.
#>
Function Test-HttpNamespaceExists {
[CmdletBinding()]
[OutputType([bool])]
Param (
[Parameter(Mandatory = $true)]
[string] $HttpNamespace
)
$namespace = Get-HttpNamespace $HttpNamespace
return $namespace -ne $null
}
<#
.SYNOPSIS
Adds a new Http Namespace. This will automatically swap out the host name for a + symbol. The
+ symbol allows the Http Namespace to bind on all NIC addresses.
#>
Function Add-HttpNamespace {
[CmdletBinding()]
[OutputType([PSObject])]
Param (
[Parameter(Mandatory = $true)]
[string] $HttpNamespace,
[Parameter(Mandatory = $true)]
[string] $DomainAccount
)
$create = $true
if(Test-HttpNamespaceExists $HttpNamespace) {
# it already exists, so maybe not create it
$create = $false
$namespace = Get-HttpNamespace $HttpNamespace
# but, if the given DomainAccount doesn't exist then create it
$user = $namespace.users |? { $_.user -eq $DomainAccount }
if($user) {
Write-Warning "NET $env:COMPUTERNAME - Http Namespace '$HttpNamespace' already contains a rule for '$DomainAccount'. Skipping creation."
return
} else {
$create = $true
}
}
if($create) {
# the standard pattern to use is http://+:port/servicename.
# eg. http://contoso01:15110/EmployeeService would become http://+:15110/EmployeeService
$plusNamespace = $HttpNamespace
$namespaceRegex = [regex] "http.*://(.*):.*/.*"
if($HttpNamespace -match $namespaceRegex) {
$hostname = $Matches[1]
$plusNamespace = $HttpNamespace.Replace($hostname, "+")
} else {
throw "NET $env:COMPUTERNAME - Http Namespace '$HttpNamespace' could not be parsed into plus format before being added. Plus format " + `
"looks like http://+:port/servicename. For example, http://contoso01:15110/EmployeeService would be formatted into " + `
"http://+:15110/EmployeeService."
}
# ensure the full domain account name is used
$fullDomainAccount = Get-FullDomainAccount $DomainAccount
# create the permission
Write-Warning "NET $env:COMPUTERNAME - Adding Http Namespace '$Httpnamespace' for account '$fullDomainAccount'"
$results = . netsh http add urlacl url=$plusNamespace user=$fullDomainAccount listen=yes delegate=yes
Write-Host "NET $env:COMPUTERNAME - Added Http Namespace '$Httpnamespace' for account '$fullDomainAccount'"
}
$namespace = Get-HttpNamespace $HttpNamespace
return $namespace
}
Where does Get-FullDomainAccount come from? Doesn't exist for me.
ReplyDeleteSorry about that. Here it is: http://stevenmaglio.blogspot.com/2016/04/get-fulldomain-account.html
ReplyDeleteNice post. Thank you.
ReplyDelete