check-tfspolicies.ps1 - sanity check your checkin policy types

I thought I would have done something more significant for my 100th blog post. Ah, well, maybe for the 200th.

I was coding an example checkin policy from scratch last night (1am insomnia, and I had already caught up on my TiVo'd Colbert Report episodes) and had screwed up something in the authoring.  I had registered the dll fine, but it wasn't showing up when I went to Add a new policy to a team project.  Unfortunately, the V1 code doesn't give you any feedback about why your assembly (well, the policies within it) don't show up.

Rather than do the checks manually, I decided it was a good opportunity to write a dorky little script that will do it for me.

It's a quick-and-dirty approach, especially as it's the kind of thing I don't expect people (myself or others) to need to run very often.

  • It doesn't hook assembly resolve, so any dependent assemblies (besides WinForms or VersionControl.Client) will need to already be loaded before running it
  • It doesn't use the ReflectionOnly* api's currently, mainly because of IsAssignableFrom behavior when one of the types is loaded through ReflectionOnly but the other isn't (and the current/default app domain may already have VersionControl.Client loaded)
  • It doesn't create and use a new app domain for the loading.

A "v2" version of the script would create a separate appdomain for the testing, use ReflectionOnly for loading, and hook assembly resolve and find/load dependent assemblies.

For the curious, I had forgotten to mark the class as Serializable.  Hey, it was 1am - give me a break!

 

 # load a couple of assemblies we expect to need
[void][reflection.assembly]::loadwithpartialname('microsoft.teamfoundation.versioncontrol.client')
[void][reflection.assembly]::loadwithpartialname('system.windows.forms')

function check_assemblies_at_path([string] $registryPath) 
{
    if (!(test-path $registryPath))
    {
        write-debug "skipping registry path which does not exist: $registryPath"
        return
    }
    $policiesKey = get-item $registryPath

    $policyAssemblyNames = $policiesKey.GetValueNames()

    foreach ($policyAssemblyName in $policyAssemblyNames)
    {
        $policyAssemblyPath = $policiesKey.GetValue($policyAssemblyName)
        if ($policyAssemblyPath)
        {
            $pathBaseName = $policyAssemblyPath -replace '.*\\(.*).dll','$1'
            if ($pathBaseName -ne $policyAssemblyName) 
            {
                write-warning ('Registry value {0} should be changed to match assembly base name {1}' -f $policyAssemblyName, $pathBaseName)
            }
            if (!(test-path $policyAssemblyPath))
            {
                write-warning "File location specified for $policyAssemblyName is missing: $policyAssemblyPath"
                continue
            }
            $policyAsm = [reflection.assembly]::LoadFrom($policyAssemblyPath)
        }
        else
        {
            $policyAsm = [reflection.assembly]::Load($policyAssemblyName)
        }
        $policyTypes = @($policyAsm.gettypes() | 
            ?{ [microsoft.teamfoundation.versioncontrol.client.ipolicydefinition].isassignablefrom($_) -and
               [microsoft.teamfoundation.versioncontrol.client.ipolicyevaluation].isassignablefrom($_) })

        write-debug ('Found {0} policy type(s) in assembly {1}: {2}' -f $policyTypes.Length, $policyAssemblyName, $policyTypes)
        foreach ($policyType in $policyTypes)
        {
            write-debug "Examining $policyType"
            if ([attribute]::getcustomattribute($policyType, [SerializableAttribute]) -eq $null)
            {
                write-warning "Policy type $policyType fails the check for [Serializable]"
            }
            if (!$policyType.IsClass)
            {
                write-warning "Policy type $policyType fails the check for being a class"
            }
            if ($policyType.IsAbstract)
            {
                write-warning "Policy type $policyType fails the check for not being abstract"
            }
            if (!$policyType.IsPublic)
            {
                write-warning "Policy type $policyType fails the check for being public"
            }
            if ($policyType.GetConstructor(@()) -eq $null)
            {
                write-warning "Policy type $policyType fails the check for having a zero-arg constructor"
            }
        }
    }    
}

check_assemblies_at_path('HKLM:\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies')
check_assemblies_at_path('HKCU:\SOFTWARE\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl\Checkin Policies')

check-tfspolicies.ps1