Tag Archives: powershell

Retrieving Properties from PSObject ExtensionData Using Reflection

Oh, PowerShell. Why do you do this to me? I run a query from your command line and see a bounty of properties, But, alas, when run from code, the properties that I expect are missing! Where could they be? ExtensionData? Ugh.

Now, it really seems like getting property values out of ExtensionData shouldn’t be so hard. It was difficult and annoying enough to me that I feel like I must be doing something wrong. If you know of a better way, please–PLEASE–let me know. Until then, I present to you a method for accessing the properties using reflection.

In this example, I’m essentially trying to access the following path of a PSObject:

psObject.ExtensionData.Value.Members
    .FirstOrDefault(x => x.Name == "propertyName").Value.Value

Things start off pretty straightforward. We want to get the ExtensionData property, which is included in the PSObject‘s Members collection.

var extensionData = psObject.Members["ExtensionData"].Value;

extensionData has its own Members collection, so we get that next. It’s not a public property, though, so we have to dig it out using reflection. Also note that we cast the object to an IEnumerable<object>

var members = extensionData.GetType()
    .GetProperty("Members", BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(extensionData) as IEnumerable<object>;

Things are starting to get a little trickier. We need to reflect on the members to find the property name that we’re looking for.

var memberType = members.First().GetType();
var nameProperty = memberType.Getproperty("Name", BindingFlags.Public | BindingFlags.Instance);
var member = members
    .Where(x => string.equals(propertyName, nameProperty.GetValue(x) as string, 
        StringComparison.InvariantCultureIgnoreCase))
    .FirstOrDefault();

Now we’re in the home stretch, and we just need to get the property value. One caveat, though: the property is a data node, so you actually need to get its value. That’s right, we need Value.Value.

var valueProperty = memberType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
var value = valueProperty.GetValue(member);
valueProperty = value.GetType().GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
return valueProperty.GetValue(value) as string;

It got kinda gross in the end there, but mission accomplished. I found that the data types weren’t preserved in the extension data, so I had to return values as strings and cast to the appropriate data type (e.g., bool) outside my function.

Here’s the complete solution. (Error/null-checks omitted for brevity.)

string GetPropertyFromExtensionData(PSObject psObject, string propertyName)
{
    var extensionData = psObject.Members["ExtensionData"].Value;

    // members = extensionData.Members as IEnumerable<object>
    var members = extensionData.GetType()
        .GetProperty("Members", BindingFlags.NonPublic | BindingFlags.Instance)
        .GetValue(extensionData) as IEnumerable<object>;

    // member = members.Where(x => x.Name == propertyName)
    var memberType = members.First().GetType();
    var nameProperty = memberType.Getproperty("Name", BindingFlags.Public | BindingFlags.Instance);
    var member = members
        .Where(x => string.equals(propertyName, nameProperty.GetValue(x) as string, 
            StringComparison.InvariantCultureIgnoreCase))
        .FirstOrDefault();

    // return member.Value.Value as string
    var valueProperty = memberType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
    var value = valueProperty.GetValue(member);
    valueProperty = value.GetType().GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
    return valueProperty.GetValue(value) as string;
}
Advertisements

Mass find & replace in TFS using Powershell

One of the problems that seems to come up semi-frequently is, “We need to update all x files to have y!” The old solution to this was manually going through all x files and updating them to have y. However, this can be accomplished quickly and easily using Powershell.
Here’s an example. We had a number of VB6 project files whose BCO paths were set to use a mapped “O:” drive that no longer existed. Instead, these projects should be using a relative path. There were 100 or so of these projects that needed to be updated, and I was able to check them out from TFS and make the change to all of them in a matter of seconds by using the following script (sorry for the formatting!):

Get-ChildItem "C:\Code" -recurse | 
    Where-Object {$_.Extension -eq ".vbp"} | 
    ForEach-Object {Write-Host "  "$_.FullName; &amp; "C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\TF.exe" checkout "$($_.FullName)" | Out-Null; (Get-Content $_.FullName) | 
        ForEach-Object {$_ -replace "O:\\.*?\\BCO\\", "..\..\BCO\"} | 
            Set-Content $_.FullName -Force}

And another example. I needed to update the revision numbers of all projects in a sub-directory to be automatic. Here’s the same script modified to accomplish that. I’ve made this more re-usable by accepting parameter values from the command line.

# example usage: .\UpdateVersion.ps1 -path "C:\Code" -build "1.0.0.*"

# get param values
param(
  [int] $build, 
  [string] $path, 
  [string] $root)


if ($build -eq $null)
{
    $build = Read-Host "Build number:"
}
if ($path -eq $null)
{
    $path = ".\"
}

# $root will be trimmed from start of directory string
# when checking for exceptions
if ($root -eq $null)
{
    $root = "c:\"
}


# $exceptionDirs will not have their versions updated
$exceptionDirs = "test"

Write-Host "Updated the following files:"

# recursively search $path for AssemblyInfo.cs
# if found, update version number &amp; save
Get-ChildItem $path -recurse | 
    Where-Object {$_.Name -eq "AssemblyInfo.cs"} | 
    Where-Object {$exceptionDirs -notcontains $_.Directory.ToString().ToUpper().TrimStart($root.ToUpper())} |
    ForEach-Object {Write-Host "  "$_.FullName; &amp; "C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\TF.exe" checkout "$($_.FullName)"; (Get-Content $_.FullName) | 
        ForEach-Object {$_ -replace "(?(\d+\.){3})\d+", "`${ver}$build"} | 
            Set-Content $_.FullName -Force}

❤ Powershell!