PowerShell Object Failure and Frustration…

PowerShell Object Failure and Frustration…

WARNING: Casual readers, this is an extremely nerdy post and will be meaningless to you unless you, like me, geek out over solving scripting problems. But, if you’re into PowerShell and are interested in my latest conundrum, read on brave souls!!

I’ve been working on a specific application pool restart script that takes a specific binding (say somesite1.example.com) and determines the specific application pool that services that binding. Then I want to enumerate that object.

In this process, I’ve discovered something utterly baffling. When I use Invoke-Command to use PowerShell remotely, I receive a completely different result than when I attempt to run this locally on the server itself. Two contrary things occur.

First, if I attempt to run a script remotely, I can enumerate all of the application pools on the server without issue and get a fully enumerated object for each pool using the following code.

$serverName = "someserver.contosso.local"
$appPoolArray = Invoke-Command -ComputerName $serverName {Import-Module WebAdministration; Get-ChildItem -Path IIS:\AppPools}
Write-Host $appPoolArray

That will get me an object that looks something like this (and I’ll get one of these for every app pool on the server):

name                        : MyAppPool
queueLength                 : 1000
autoStart                   : True
enable32BitAppOnWin64       : False
managedRuntimeVersion       : v4.0
managedRuntimeLoader        : webengine4.dll
enableConfigurationOverride : True
managedPipelineMode         : Integrated
CLRConfigFile               : 
passAnonymousToken          : True
startMode                   : OnDemand
state                       : Started
applicationPoolSid          : S-1-5-82-99999-99999-99999
processModel                : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
recycling                   : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
failure                     : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
cpu                         : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
workerProcesses             : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
ItemXPath                   : /system.applicationHost/applicationPools/add[@name='MyAppPool']
PSPath                      : WebAdministration::\\SOMESERVER\AppPools\MyAppPool
PSParentPath                : WebAdministration::\\SOMESERVER\AppPools
PSChildName                 : UserService
PSDrive                     : IIS
PSProvider                  : WebAdministration
PSIsContainer               : True
PSComputerName              : someserver.contosso.local
RunspaceId                  : 8f23b89-9999-9999-
Attributes                  : {Microsoft.IIs.PowerShell.Framework.ConfigurationAttribute, Microsoft.IIs.PowerShell.Framework.ConfigurationAttribute, 
                              Microsoft.IIs.PowerShell.Framework.ConfigurationAttribute, Microsoft.IIs.PowerShell.Framework.ConfigurationAttribute...}
ChildElements               : {Microsoft.IIs.PowerShell.Framework.ConfigurationElement, Microsoft.IIs.PowerShell.Framework.ConfigurationElement, 
                              Microsoft.IIs.PowerShell.Framework.ConfigurationElement, Microsoft.IIs.PowerShell.Framework.ConfigurationElement...}
ElementTagName              : add
Methods                     : {Microsoft.IIs.PowerShell.Framework.ConfigurationMethod, Microsoft.IIs.PowerShell.Framework.ConfigurationMethod, 
                              Microsoft.IIs.PowerShell.Framework.ConfigurationMethod}
Schema                      : Microsoft.IIs.PowerShell.Framework.ConfigurationElementSchema

However, if I attempt to run this very same script locally (without the Invoke-Command block), I get something bizarrely different. Here’s the code:

Import-Module WebAdministration
$appPoolArray = Get-ChildItem -Path IIS:\AppPools
Write-Host $appPoolArray

Notice that the only real difference here is the lack of the Invoke-Command – it’s still an object built with Get-ChildItem on the same path… just that it’s local now and not remote. Here’s the result:

Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement Microsoft.IIs.PowerShell.Framework.ConfigurationElement

One object over and over again. Why does this script when run locally only provide a ConfigurationElement object that isn’t enumerated but when I run it remotely, I get a nice neat list of all of the objects? No idea. Here’s where it gets even weirder. The IIS object that gets created with the WebAdministration module is a clever little feat, in that, you can browse it as if it were a directory. Neat, huh? I thought so, too. So, the object in the example above is based on IIS:\AppPools. There’s also an IIS:\Sites. The weird thing? I get exactly the opposite problem. I cannot enumerate IIS:\Sites remotely at all, but I can run it locally.

Here’s the remote example:

$serverName = "someserver.contosso.local"
$siteArray = Invoke-Command -ComputerName $serverName {Import-Module WebAdinistration; Get-ChildItem -Path IIS:\Sites}
Write-Host $siteArray

Here’s the result:

Invalid class string (Exception from HRESULT: 0x800401F3 (CO_E_CLASSSTRING))
    + CategoryInfo          : NotSpecified: (:) [Get-ChildItem], COMException
    + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Microsoft.PowerShell.Commands.GetChildItemCommand
    + PSComputerName        : someserver.contosso.local

Mmkay… what the heck, right? So, I perform the opposite and run this on the server itself and it only yields the ConfigurationElement series that I saw in the code above when attempting to enumerate the pool objects locally. Not to be dissuaded, I try something else. I decide to loop through the sites with a Foreach loop to see what I can get this way. (I found the code below online, but I failed to record where… I was just doing so many searches, so if this is yours, drop me a line and I’ll credit you)

Import-Module WebAdministration
$matchURL = "*somsitename.contosso.com*"
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {

    $Binding = $Site.bindings
    [string]$BindingInfo = $Binding.Collection
    [string]$IP = $BindingInfo.SubString($BindingInfo.IndexOf(" "),$BindingInfo.IndexOf(":")-$BindingInfo.IndexOf(" "))         
    [string]$Port = $BindingInfo.SubString($BindingInfo.IndexOf(":")+1,$BindingInfo.LastIndexOf(":")-$BindingInfo.IndexOf(":")-1) 

    if ($Port -like $matchURL) {
        $siteName = $Site.name
        break
        }

    if ($Site.enabledProtocols -eq "http") {
        #DO CHECKS HERE     
    }
    elseif($site.enabledProtocols -eq "https") {
        #DO CHECKS HERE
    }
}

Write-Host $siteName

This worked wonderfully and I was able to get the exact site name from a string comparison on the URL to the site bindings. Emboldened, I took it one step further to try and get the app pool object to enumerate it. I added the following:

$sitePath = "IIS:\Sites\" + $siteName
$siteObject = Get-Item $sitePath
$appPool = $siteObject.applicationPool
$appPoolPath = "IIS:\AppPools\" + $appPool
$appPoolObject = Get-Item $appPoolPath
Write-Host $appPoolObject

As you might have guessed by now… this didn’t work either. It yielded another obscure PowerShell object that I can’t seem to break down into anything useful.

Microsoft.IIs.PowerShell.Framework.NodeCollection

And if I switched that code up and decided to use Get-Item instead of Get-ChildItem (in case I’d misunderstood the nested level), it was not an improvement. I just got:

Microsoft.IIs.PowerShell.Framework.ConfigurationElement

Needless to say, I’m now baffled. I don’t know how to get at this object. Ideally, I would like to run the script remotely that takes a URL and determines which site’s binding it matches to determine the name of the app pool, but none of the methods I’m using seem to be working. Please, drop me a line if you see something glaring that I’m missing. I’ll welcome the input gladly (and, yes, I’ve posted this to Stack Overflow).