# encoding: ascii
# api: powershell
# title: Add-Namespace
# description: Version 1.1
# version: 1.1
# type: script
# author: George Howarth
# license: CC0
# function: Add-Namespace
# x-poshcode-id: 1972
# x-archived: 2015-06-23T21:08:13
# x-published: 2011-07-13T15:07:00
#
# Imports the types in the specified namespaces in the specified assemblies.
# I plan to support -WhatIf in a future version.
# Changes from V1.0:
# - Cleaned up type resolution error message – Added Get-ExecutingAssemblies function – Optimized main algorithm slightly
# Special thanks to Oisin Grehan for his great work
#
trap [System.Management.Automation.RuntimeException]
{
$entryException = $_
if ($_.CategoryInfo.Category -eq [System.Management.Automation.ErrorCategory]::InvalidOperation)
{
if ($_.FullyQualifiedErrorId -eq "TypeNotFound")
{
$targetName = $_.CategoryInfo.TargetName
try
{
$isAmbiguous = $global:__ambiguousTypeNames.Contains($targetName)
}
catch
{
throw $entryException
}
if ($isAmbiguous)
{
$message = New-Object System.Text.StringBuilder
$message.AppendFormat("The type [{0}] is ambiguous. Specify one of the following: ", $targetName).AppendLine() | Out-Null
[System.Type]::GetType("System.Management.Automation.TypeAccelerators")::Get.GetEnumerator() | ForEach-Object {
if (($_.Key.Split('.'))[-1] -eq $targetName)
{
$message.Append($_.Key).AppendLine() | Out-Null
}
}
$message.AppendLine() | Out-Null
$message.AppendFormat("At {0}:{1} char:{2}", $_.InvocationInfo.ScriptName, $_.InvocationInfo.ScriptLineNumber, $_.InvocationInfo.OffsetInLine).AppendLine() | Out-Null
$erroneousToken = $_.InvocationInfo.Line.Substring(0, $_.InvocationInfo.OffsetInLine - 1)
$remainder = $_.InvocationInfo.Line.Substring($_.InvocationInfo.OffsetInLine - 1)
$message.AppendFormat("+ {0} <<<< {1}", $erroneousToken, $remainder).AppendLine() | Out-Null
$message.AppendFormat("`t+ CategoryInfo : {0} : ({1}:{2}) [], {3}", $_.CategoryInfo.Category, $targetName, $_.TargetObject.GetType().Name, $_.Exception.GetType().Name).AppendLine() | Out-Null
$message.AppendFormat("`t+ FullyQualifiedErrorId : {0}", $_.FullyQualifiedErrorId).AppendLine() | Out-Null
Write-Host $message.ToString() -ForegroundColor Red
continue
}
}
}
}
<#
.SYNOPSIS
Imports the types in the specified namespaces in the specified assemblies.
.DESCRIPTION
The Add-Namespace function adds a type accelerator for each type found in the specified namespaces in the specified assemblies that satisfy a set of conditions. For more information see the NOTES section.
.PARAMETER Assembly
Specifies one or more assemblies to import the specified namespaces from.
.PARAMETER Namespace
Specifies one or more namespaces to import.
.INPUTS
System.Reflection.Assembly
You can pipe an assembly to Add-Namespace.
.OUTPUTS
None
This function does not return any output.
.NOTES
The type accelerator for the type is added if the type:
- Has a base type which is not System.Attribute, System.Delegate or System.MulticastDelegate
- Is not abstract
- Is not an interface
- Is not nested
- Is public
- Is visible
- Is qualified by the namespace specified in the Namespace parameter
This function also comes with an exception handler in the form of a trap block. Type name collisions occur when a type has the same name of another type which is in a different namespace. When this happens, the function adds or replaces the type accelerator for that type using its fully-qualified type name. If a type resolution occurs during runtime, the trap block will determine if the type was unresolved during any of the calls made to Add-Namespace and throw an exception listing valid replacements.
Be aware that namspaces can span multiple assemblies, in which case you would have to import the namespace for each assembly that it exists in.
This function will not attempt to add or replace types which already exist under the same name.
This function assumes that the variable $global:__ambiguousTypeNames is exclusively available for use.
The type accelerators added by this function exist only in the current session. To use the type accelerators in all sessions, add them to your Windows PowerShell profile. For more information about the profile, see about_profiles.
.EXAMPLE
C:\PS> [System.Reflection.Assembly]::LoadWithPartialName("mscorlib") | Add-Namespace System.Reflection
C:\PS> [Assembly]::LoadWithPartialName("System.Windows.Forms")
This example shows how to import namespaces from an assembly. The assembly must be loaded non-reflectively into the current application domain.
.EXAMPLE
C:\PS> $assemblies = Get-ExecutingAssemblies -Filter mscorlib, System, System.Windows.Forms, System.Xml
C:\PS> $assemblies | Add-Namespace System, System.Collections, System.Collections.Generic, System.Net, System.Net.NetworkInformation, System.Reflection, System.Windows.Forms, System.Xml
This example shows how to import multiple namespaces from multiple assemblies using the Get-ExecutingAssemblies function to filter the required assemblies.
.LINK
about_trap
#>
function Add-Namespace
{
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(ValueFromPipeline = $true)]
[System.Reflection.Assembly]$Assembly,
[Parameter(Mandatory = $true, Position = 0)]
[ValidateNotNullOrEmpty()]
[String[]]$Namespace
)
begin
{
if ($global:__ambiguousTypeNames -eq $null)
{
$global:__ambiguousTypeNames = New-Object 'System.Collections.Generic.List[System.String]'
}
$genericRegex = [Regex]'(?<Name>.*)`\d+'
$typeAccelerators = [System.Type]::GetType("System.Management.Automation.TypeAccelerators")
$typeDictionary = $typeAccelerators::Get
}
process
{
$_.GetTypes() | Where-Object {
($_.BaseType -ne [System.Attribute]) -and
($_.BaseType -ne [System.Delegate]) -and
($_.BaseType -ne [System.MulticastDelegate]) -and
!$_.IsAbstract -and
!$_.IsInterface -and
!$_.IsNested -and
$_.IsPublic -and
$_.IsVisible -and
($_.Namespace -ne $null) -and
($Namespace -contains $_.Namespace)
} | ForEach-Object {
$name = $_.Name
$fullName = $_.FullName
if ($_.IsGenericType)
{
if ($_.FullName -match $genericRegex)
{
$fullName = $Matches["Name"]
$name = $fullName.Split('.')[-1]
}
}
if ($typeDictionary.ContainsKey($name))
{
if ($typeDictionary[$name] -eq $_)
{
return
}
}
if ($typeDictionary.ContainsKey($fullName))
{
if ($typeDictionary[$fullName] -eq $_)
{
return
}
}
if ($global:__ambiguousTypeNames.Contains($name))
{
$typeAccelerators::Add($fullName, $_)
return
}
if ($typeDictionary.ContainsKey($name))
{
$type = $typeDictionary[$name]
if ($_ -ne $type)
{
$global:__ambiguousTypeNames.Add($name)
$newName = $type.FullName
if ($type.IsGenericType)
{
if ($newName -match $genericRegex)
{
$newName = $Matches["Name"]
}
}
$typeAccelerators::Remove($name)
$typeAccelerators::Add($newName, $type)
$typeAccelerators::Add($fullName, $_)
}
return
}
$typeAccelerators::Add($name, $_)
} | Out-Null
}
end { }
}
<#
.SYNOPSIS
Gets assemblies that are currently executing in the current application domain.
.DESCRIPTION
The Get-Assemblies function retrieves Assembly objects from assemblies that are executing in the current application domain.
.PARAMETER Filter
Specifies one or more Assembly objects to retrieve.
.INPUTS
None
You cannot pipe objects to Get-ExecutingAssemblies.
.OUTPUTS
System.Reflection.Assembly
A collection of Assembly objects.
.NOTES
You can get a list of currently executing assemblies by calling this function. Sometimes, it is not necessary to load some assemblies as they are loaded into the current application domain by default.
.EXAMPLE
C:\PS> $assemblies = Get-ExecutingAssemblies -Filter mscorlib, System, System.Windows.Forms
C:\PS> $assemblies | Add-Namespace System, System.Collections, System.Collections.Generic, System.Net, System.Net.NetworkInformation, System.Reflection, System.Windows.Forms
This example shows how Get-ExecutingAssemblies can be used in conjunction with Add-Namespace.
.LINK
#>
function Get-ExecutingAssemblies
{
param (
[Parameter(Mandatory = $false, Position = 0)]
[String[]]$Filter
)
if (($Filter -eq $null) -or ($Filter.Length -eq 0))
{
return [System.AppDomain]::CurrentDomain.GetAssemblies()
}
return [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $Filter -contains [System.IO.Path]::GetFileNameWithoutExtension($_.Location) }
}
# Sample usage
# You can do this as an initialization task for your script
Get-ExecutingAssemblies -Filter mscorlib, System, System.Windows.Forms, System.Xml | Add-Namespace -Namespace `
System,
System.Collections,
System.Collections.Generic,
System.Net,
System.Net.NetworkInformation,
System.Reflection,
System.Windows.Forms,
System.Xml