Export Powershell Objects to Xml – in a more natural format than Export-CliXml

Introduction

On a recent project I was calling a PowerShell script and wanted to return Xml from the script so that I could iterate on an object using Linq to Xml. I investigated options available for exporting an object in Powershell to Xml and looked at some built-in cmdlets like Export-CliXml: http://technet.microsoft.com/en-us/library/dd347657.aspx, but I could not get over how complicated my Linq to Xml query would have to be for relatively simple objects. The Export-CliXml format is extremely verbose and is not really easy to work with once you get the Xml out.

Technique

The following snippet shows how to output the Xml and uses custom serialization to Xml for the SamlEndpoint type which shows up as the type name until you drill into the type to get the property information.

function DumpObjectToXml($obj) {
$a = $obj

$openingTag = "<" + $a.GetType().Name + ">"

$ret = $ret + $openingTag

Get-Member -InputObject $a -MemberType Properties | ForEach-Object {

$CurrentName = $_.Name

$a.GetType().GetProperties() | ? { $_.Name -eq $CurrentName } | ForEach-Object {

$specialSerialization = $false

# Handle specialized serialization for object properties of parent object

if ($_.Name -eq "SamlEndpoints") {

if ($obj.SamlEndpoints -ne $null) {

$d = $obj.SamlEndpoints | ? { $_.BindingUri -like "*HTTP-POST" }

$val = $d.Location.AbsoluteUri

$specialSerialization= $true

}

else {

$val = ""

}

}

if ($_.CanRead -eq $true -and $specialSerialization -eq $false) {

$val = $_.GetValue($a, $null)

}

}

$out = "<" + $_.Name + ">" + $val + "</" + $_.Name + ">"

$ret = $ret + $out

}

$closingTag = "</" + $a.GetType().Name + ">"

$ret = $ret + $closingTag

return $ret

}

This can be helpful for outputting a list of objects returned by another cmdlet as Xml. Here is a simple example that builds a usable Xml element for the relying parties returned from the built-in ADFS cmdlet:


$rpOut = "<RelyingPartyTrusts>"

Get-ADFSRelyingPartyTrust | ForEach-Object { $rpOut = $rpOut + (DumpObjectToXml -obj $_) }

$rpOut = $rpOut + "</RelyingPartyTrusts>"

The resulting output looks like this:

<RelyingPartyTrusts>
<RelyingPartyTrust>
<AutoUpdateEnabled>False</AutoUpdateEnabled>
<ClaimsAccepted>Microsoft.IdentityServer.PowerShell.Resources.ClaimDescription Microsoft.IdentityServer.PowerShell.Resources.ClaimDescription</ClaimsAccepted>
<ConflictWithPublishedPolicy>False</ConflictWithPublishedPolicy>
<DelegationAuthorizationRul
es></DelegationAuthorizationRules>
<Enabled>False</Enabled><EncryptClaims>False</EncryptClaims>
<Identifier>https://myAdfsServer/ClaimsAwareWebAppWithManagedSTS/</Identifier>
...
</RelyingPartyTrust>
</RelyingPartyTrusts>

Conclusion

This technique of using objects returned from PowerShell can be very valuable when you want to report on objects that may only be exposed through a PowerShell interface and no supported or available .NET API. There are some limitations to this approach though. Not all objects returned from some cmdlets have properties that can be read in an isolated, atomic way. When trying this technique with Get-Process errors occur indicating the services must be stopped before reading the properties. I think this is due to the way the cmdlet is coded. I tested this script with many of the ADFS cmdlets and it is working well.

ADFS Error Tips: When your federation service name does not match your FQDN

With ADFS I am really starting to think it would be useful to have a way to just run a PowerShell test script to diagnose issues, much like a series of unit tests, maybe call it configuration test. Many of the configuration challenges with ADFS could be diagnosed with some scripts. So I made one here.

A few days ago I was getting the following error on ADFS (single server):

ID1038: The AudienceRestrictionCondition was not valid because the specified Audience is not present in AudienceUris.

Audience: ‘https://<fqdn>/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256&#8217;

at Microsoft.IdentityModel.Tokens.SamlSecurityTokenRequirement.ValidateAudienceRestriction(IList`1 allowedAudienceUris, IList`1 tokenAudiences)

at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateConditions(Saml2Conditions conditions, Boolean enforceAudienceRestriction)

at Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateToken(SecurityToken token)

at Microsoft.IdentityServer.Service.Tokens.MSISSaml2TokenHandler.ValidateToken(SecurityToken token)

at Microsoft.IdentityModel.Tokens.WrappedSaml2SecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

at Microsoft.IdentityModel.Tokens.WrappedSamlSecurityTokenAuthenticator.ValidateTokenCore(SecurityToken token)

at System.IdentityModel.Selectors.SecurityTokenAuthenticator.ValidateToken(SecurityToken token)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver, IList`1 allowedTokenAuthenticators, SecurityTokenAuthenticator&amp; usedTokenAuthenticator)

at System.ServiceModel.Security.ReceiveSecurityHeader.ReadToken(XmlDictionaryReader reader, Int32 position, Byte[] decryptedBuffer, SecurityToken encryptionToken, String idInEncryptedForm, TimeSpan timeout)

at System.ServiceModel.Security.ReceiveSecurityHeader.ExecuteFullPass(XmlDictionaryReader reader)

at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout, ChannelBinding channelBinding, ExtendedProtectionPolicy extendedProtectionPolicy)

at System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, TimeSpan timeout)

at System.ServiceModel.Security.TransportSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout)

at System.ServiceModel.Security.SecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Channels.SecurityChannelListener`1.ServerSecurityChannel`1.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationState)

at System.ServiceModel.Channels.SecurityChannelListener`1.SecurityReplyChannel.ProcessReceivedRequest(RequestContext requestContext, TimeSpan timeout)

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveRequestAndVerifySecurityAsyncResult.ProcessInnerItem(RequestContext innerItem, TimeSpan timeout)

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.OnInnerReceiveDone()

at System.ServiceModel.Channels.SecurityChannelListener`1.ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)

at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)

at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)

at System.ServiceModel.Channels.InputQueue`1.AsyncQueueReader.Set(Item item)

at System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.InputQueue`1.EnqueueAndDispatch(T item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.InputQueueChannel`1.EnqueueAndDispatch(TDisposable item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback, Boolean canDispatchOnThisThread)

at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, ItemDequeuedCallback dequeuedCallback)

at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, ItemDequeuedCallback callback)

at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult result)

at System.ServiceModel.Diagnostics.Utility.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)

at System.Net.LazyAsyncResult.Complete(IntPtr userToken)

at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)

at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)

at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)


I had already enabled service and server tracing and had made sure all my certificate configuration was correct. Eventually I figured out the problem was that my federation service name was different from my server FQDN. When looking at the server trace I was seeing a different address being shown. This difference also occurs when the federation metadata exposes a different name than the current server FQDN.

Here is a script I made to check for this scenario:

Write-Host "Testing for correct federation service name"

Add-PSSnapin Microsoft.ADFS.PowerShell

$IPconfig = Get-WmiObject Win32_NetworkAdapterConfiguration | where {$_.IPaddress -like "192.*" }  # modify this as-needed

# Iterate and get IP address
$ip = $IPconfig.IPaddress
$fqdn = [System.Net.DNS]::GetHostByAddress($ip)

$fedServiceName = Get-ADFSProperties | Where { $_.Name -eq "HostName" }

if ($fedServiceName -ne $fqdn)  { Write-Host "FAIL: Server FQDN not equal to Federation Service Name." }

Remove-PSSnapin Microsoft.ADFS.PowerShell

ADFS 2 Migration Tips

Introduction

Today I was working on moving my ADFS environment to a separate VM so I could test out a deployment guide I had been working on. On my VM1 I had installed ADFS with Windows Internal Database (WID) in a standalone mode. In my target VM2 I had installed ADFS on SQL 2008 R1 as a farm. I wanted to easily move all of the ADFS configuration and settings to VM2 but found that this is actually fairly difficult.

Attempts

Database Backup / Restore

The first thing I tried to do was to create a backup of the AdfsConfiguration and AdfsArtifactStore databases from VM1 and try to restore them on VM2. Before trying this I made a backup of my existing ADFS databases. Because the SQL version used with WID is SQL 2005 it is not possible to use a .bak file exported from WID and restored on SQL 2008, you get an error. So then I had to detach the files from the WID database and try to reattach the databases on the SQL 2008 VM. I was able to reattach the databases but there were problems afterward.

In order to detach the existing SQL 2008 AdfsConfiguration database on VM2, I had to stop the ADFS 2 service. Then after reattaching the new databases from WID, the ADFS 2 service would not start. So I stopped at this point and switched over to moving the artifacts using scripts. If anyone had any other tips for making this type of migration work, I would appreciate the feedback!

Scripted Move

After not being able to move the data over through database backups, I decided to try to script everything. This approach is just as challenging because you still need to go through all of the objects in the ADFS databases and this can take some time in building out the new scripts. I realized a tool that can generate the scripts for you would be a huge time saver.

Some of the PowerShell cmdlets for ADFS use parameters typed as System.Security.Cryptography.X509Certificates.X509Certificate2. So the parameters expect that you will be passing an object rather than the common name string. When I first saw this in the API reference, I started trying to translate .NET code over for pulling a certificate from the stores like in the code on this article: http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509certificate2.aspx.

Translating the code from C# to PowerShell was very difficult and somewhat error prone. Eventually I gave up and looked for a better option. I finally found out that the certificate stores load as a PowerShell drive in PowerShell 2.0 and the certificates surface as X509Certificate2 objects. Here is an overview article on the certificate provider: http://technet.microsoft.com/en-us/library/dd347615.aspx. The certificate provider makes it very easy to reference a certificate when working with the ADFS API.

I was moving over claim issuers and relying parties that had mapped claim rules attached to them. The basic approach for handling this was to use the “Get-” cmdlet to output the claim rules, copy the rules to a text file, and then run the “Set-X-RulesFile” to reimport the rules. This is easier to understand when seen in an example.

  1. First I am going to get all of the claim issuers output to the PowerShell window:
    Get-ADFSClaimsProviderTrust
    

    This outputs something like this:

    AcceptanceTransformRules : @RuleTemplate = “PassThroughClaims”
    @RuleName = “Pass thru role”
    c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/role”%5D
    => issue(claim = c);@RuleTemplate = “PassThroughClaims”
    @RuleName = “Pass Thru Name”
    c:[Type == “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name”%5D
    => issue(claim = c);

    I just take everything after the colon on the first line and copy to a text file called acceptanceTransformRules.txt.

  2. Finally, I am going to import a claims issuer’s rules (the claim issuer MySTS needs to already exist at this point:
    # Refer to the rules file with an absolute path
    $curdir = Get-Location
    $curdirfile = Join-Path -Path $curdir -ChildPath "acceptanceTransformRules.txt"
    
    # Import the claim rules
    Set-ADFSClaimsProviderTrust -TargetName "MySTS" -AcceptanceTransformRulesFile $curdirfile
    

Moving a claims issuer from one server to another is resolved to two basic steps: 1. Installing the certificates, 2. Running a script referencing the installed certificates. Here is a PowerShell example combining the ADFS and Certificate APIs for creating a new issuer:


Write-Host "Loading ADFS Snap-In"
Add-PSSnapin Microsoft.ADFS.PowerShell

$certname = "CN=localhost"

# Getting the certificate. How simple using the certificate provider!
set-location cert:\localmachine\my
$cert = Get-ChildItem | Where { $_.Subject -eq $certname }

Set-Location c:

# certificates are correctly specified here
Add-ADFSClaimsProviderTrust -Name "MySTS" -Identifier "https://localhost/trust" -MonitoringEnabled $false -TokenSigningCertificate $cert -EncryptionCertificate $cert -AutoUpdateEnabled $false -AllowCreate $True

Write-Host "Disabling CRL checking on claims issuer trust certs because these are self-signed"
Set-ADFSClaimsProviderTrust -TargetName "MySTS" -SigningCertificateRevocationCheck "None"
Set-ADFSClaimsProviderTrust -TargetName "MySTS" -EncryptionCertificateRevocationCheck "None"

Conclusion

After going through the process of creating scripts like this I realized it is not too much work to do a scripted move but it would be much nicer if you could just export to script from the ADFS mmc or execute some other cmdlet to handle this whole process for you. Maybe in the future I might try to spend some time making something like this and putting it on CodePlex. Thanks!

Time saving PowerShell Snippets for BizTalk Part 1

Some of the beauty of using PowerShell is the ability to rapidly do things that would take a long time to do in .NET code. But you can do this without opening Visual Studio. I have found many uses for PowerShell in working with BizTalk. One area it comes in handy is working with BizTalk binding files. I know you can just use the ExplorerOM but this is painful due to its 32-bit limitations. A frequent pain-point in deploying across environments is needing to create external artifacts for supporting your BizTalk ports.

So for example you use MSMQ and need to create all the queues that your ports refer to. Or directories for all the FILE ports you use. It is nice to just include some more PowerShell to handle this and just rest assured the external artifact will get created when you deploy your BizTalk updates. In my PowerShell code below it handles creation of folders. Enjoy!

param([string]$pathToBindingsFile,[string]$rootCreatePath)

$newroot = ""

if($rootCreatePath -ne "") {
    $rootCreatePathExists = [io.file]::Exists($rootCreatePath)

    if($rootCreatePathExists -eq $true) {  $newroot = $rootCreatePath }
    else {

        Write-Host "Creating directory" $rootCreatePath
        [io.directory]::CreateDirectory($rootCreatePath)

        $newroot = $rootCreatePath
    }

}

$bindingFileExists = [io.file]::Exists($pathToBindingsFile)

if($bindingFileExists -eq $true) {
    
    # Read in the binding file content
    $bindingContent = [xml](Get-Content $pathToBindingsFile)
    $bindingContent.SelectNodes("//PrimaryTransport") | ForEach-Object {
    
        # Only process if the port type is FILE
        if ($_.TransportType.Name -eq "FILE") {
        
            $directory = [io.path]::GetDirectoryName($_.Address)
        
            # replace with rootCreatePath if this value exists
            if ($newroot.Trim() -ne "") {
                $directory = $directory.Replace($defaultRoot,$newRoot)
            }
           
            
            # Check if the directory exists
            $directoryExists = [io.directory]::Exists($directory)
            if ($directoryExists -ne $true) {
                Write-Host "New directory:" $directory
                [io.directory]::CreateDirectory($directory)
            }
        }
    }
    
}
else {
    Write-Host "Binding file does not exist:" $pathToBindingsFile
}


Tips for your profile.ps1 file when using BizTalk with Powershell

Introduction

I have been meaning for a while to add some PowerShell posts to my blog but had been busy lately. I have been using the BizTalk PowerShell provider (http://psbiztalk.codeplex.com) that my friend Randal van Splunteren helped create. I now use PowerShell  all the time in my BizTalk work and find it to be very helpful for common administrative tasks like deploying pipeline components, exporting backups of binding files, and handling automated deployments. I am definitely not a PowerShell expert but more of an enthusiast at this point. I will post some of the tips and tricks I have found in using PowerShell for BizTalk development. I am not going to introduce PowerShell for beginners but approach this as giving you some interesting, useful scripts.

I have found it typically difficult to find lots of useful examples of BizTalk PowerShell. So I aim to change this by providing some of mine so that other people can take advantage of them.

Today I am going to show most of my profile.ps1 file which is called by PowerShell when starting up the shells.

Details

Working with PowerShell and BizTalk is still a relatively involved task. This is primarily because of BizTalk’s still heavy dependence on x86 architecture. The install guide for the BizTalk PowerShell provider mentions you need to load the 32-bit version of the PowerShell shell to add the snap-in. When I first started working with this provider I kept loading the 64-bit provider and I kept getting this error:

Add-PSSnapin : The Windows PowerShell snap-in ‘BizTalkFactory.Powershell.Extensions’ is not installed on this machine.
At line:1 char:13
+ Add-PSSnapin <<<<  BizTalkFactory.Powershell.Extensions
    + CategoryInfo          : InvalidArgument: (BizTalkFactory.Powershell.Extensions:Stri
   ng) [Add-PSSnapin], PSArgumentException
    + FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.AddPSSnapinCommand

So be sure to load the 32-bit shell. There are quite a few challenges for the BizTalk PowerShell scripter because the SQL provider is based on 64-bit architecture. In my daily work I often want to kick off a SQL agent job so this requires managing more than one PowerShell instance at a time. For this reason, there is some complexity in loading all of the useful providers. It is handy to do this provider loading in the profile.ps1 file. To simplify my work, I also set a few executable aliases that I frequently call during deployment of BizTalk artifacts. Most of my profile.ps1 file is shown below:

# This script is for a 64-bit system
if(([diagnostics.process]::GetCurrentProcess()).path -match '\\syswow64\\') {

	Write-Host "32-bit Powershell"

	Write-Host "Loading Powershell provider for BizTalk snap-in"
	Add-PSSnapin BizTalkFactory.Powershell.Extensions

	New-PSDrive -Name BTS -PSProvider BizTalk -Root BTS:\ -Instance "." -Database BizTalkMgmtDb
	Set-Location -Path BTS:
}
else {

	Write-Host "64-bit Powershell"

	Write-Host "Loading SQL Provider"           # from http://msdn.microsoft.com/en-us/library/cc281962.aspx
    $ErrorActionPreference = "Stop"
    $sqlpsreg="HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.SqlServer.Management.PowerShell.sqlps"

    if (Get-ChildItem $sqlpsreg -ErrorAction "SilentlyContinue")  {
       throw "SQL Server Provider is not installed."
    }
    else {
        $item = Get-ItemProperty $sqlpsreg
        $sqlpsPath = [System.IO.Path]::GetDirectoryName($item.Path)
    }

    # Set mandatory variables for the SQL Server rovider
    Set-Variable -scope Global -name SqlServerMaximumChildItems -Value 0
    Set-Variable -scope Global -name SqlServerConnectionTimeout -Value 30
    Set-Variable -scope Global -name SqlServerIncludeSystemObjects -Value $false
    Set-Variable -scope Global -name SqlServerMaximumTabCompletion -Value 1000

    # Load the snapins, type data, format data
    Push-Location
    cd $sqlpsPath
    Add-PSSnapin SqlServerCmdletSnapin100
    Add-PSSnapin SqlServerProviderSnapin100
    Update-TypeData -PrependPath SQLProvider.Types.ps1xml
    update-FormatData -prependpath SQLProvider.Format.ps1xml
    Pop-Location

    # More of my code
    New-PSDrive -Name localSql -PSProvider SqlServer -Root SQLSERVER:\SQL\Bencpc
    Set-Location -Path C:

}

# Setup some useful shortcuts for commonly used executables in BizTalk deployment
Set-Alias gac "C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe"
Set-Alias msbuild "C:\Windows\Microsoft.NET\Framework\v3.5\msbuild.exe"
Set-Alias btstask "C:\Program Files (x86)\Microsoft BizTalk Server 2010\btstask.exe"

Blog at WordPress.com.

Up ↑