# encoding: ascii
# api: csharp
# title: [ResolvePaths()]
# description: A long time ago, I wrote a ResolvePaths attribute which I never really published.
# version: 0.1
# type: script
# author: Joel Bennett
# license: CC0
# function: Get-Path
# x-poshcode-id: 6645
# x-archived: 2016-12-11T05:17:05
# x-published: 2016-12-08T21:44:00
#
# This is a dump of the last copy of it that I can find, in case I never finish it.
#
Add-Type -ErrorAction Stop <# -path $PSScriptRoot\FileSystemPath.cs #> @'
using System;
using System.ComponentModel;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Text.RegularExpressions;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ResolvePathsAttribute : ArgumentTransformationAttribute {
public enum PathType { Simple, Provider, Drive, Relative }
public PathType ResolveAs { get; set; }
public override string ToString() {
return "[ResolvePaths(" + ((ResolveAs != PathType.Simple) ? "ResolveAs=\"" + ResolveAs + "\")]" : ")]");
}
public override Object Transform( EngineIntrinsics engine, Object inputData) {
// standard workaround for the initial bind when pipeline data hasn't arrived
if(inputData == null) { return null; }
ProviderInfo provider = null;
PSDriveInfo drive = null;
Collection<string> results = new Collection<string>();
Collection<string> providerPaths = new Collection<string>();
var PSPath = engine.SessionState.Path;
var inputPaths = new Collection<string>();
try {
// in order to not duplicate code, always treat it as an object array
var inputArray = inputData as object[];
if(inputArray == null) { inputArray = new object[]{inputData}; }
foreach(var input in inputArray) {
// work around ToString() problem in FileSystemInfo
var fsi = input as System.IO.FileSystemInfo;
if(fsi != null) {
inputPaths.Add(fsi.FullName);
} else {
// work around FileSystemInfo actually being a PSObject
var psO = input as System.Management.Automation.PSObject;
if(psO != null) {
fsi = psO.BaseObject as System.IO.FileSystemInfo;
if(fsi != null) {
inputPaths.Add(fsi.FullName);
} else {
inputPaths.Add(psO.BaseObject.ToString());
}
} else {
inputPaths.Add(input.ToString());
}
}
}
foreach(string inputPath in inputPaths) {
if(WildcardPattern.ContainsWildcardCharacters(inputPath)) {
providerPaths = PSPath.GetResolvedProviderPathFromPSPath(inputPath, out provider);
} else {
providerPaths.Add(PSPath.GetUnresolvedProviderPathFromPSPath(inputPath, out provider, out drive));
}
foreach(string path in providerPaths) {
var newPath = path;
if(ResolveAs == PathType.Provider && !PSPath.IsProviderQualified(newPath)) {
newPath = provider.Name + "::" + newPath;
}
else if(ResolveAs != PathType.Provider && PSPath.IsProviderQualified(newPath))
{
newPath = Regex.Replace( newPath, Regex.Escape( provider.Name + "::" ), "");
}
if(ResolveAs == PathType.Drive)
{
string driveName;
if(!PSPath.IsPSAbsolute(newPath, out driveName)) {
if(drive == null) {
newPath = PSPath.GetUnresolvedProviderPathFromPSPath(newPath, out provider, out drive);
}
if(!PSPath.IsPSAbsolute(newPath, out driveName)) {
newPath = drive.Name + ":\\" + PSPath.NormalizeRelativePath( newPath, drive.Root );
}
}
} else if(ResolveAs == PathType.Relative) {
var currentPath = PSPath.CurrentProviderLocation(provider.Name).ProviderPath.TrimEnd(new[]{'\\','/'});
var relativePath = Regex.Replace(newPath, "^" + Regex.Escape(currentPath), "", RegexOptions.IgnoreCase);
// Console.WriteLine("currentPath: " + currentPath + " || relativePath: " + relativePath);
if(relativePath != newPath) {
newPath = ".\\" + relativePath.TrimStart(new[]{'\\'});
} else {
try {
newPath = PSPath.NormalizeRelativePath(newPath, currentPath);
// Console.WriteLine("currentPath: " + currentPath + " || relativePath: " + relativePath + " || newPath: " + newPath);
} catch {
newPath = relativePath;
}
}
}
results.Add(newPath);
}
}
} catch (ArgumentTransformationMetadataException) {
throw;
} catch (Exception e) {
throw new ArgumentTransformationMetadataException(string.Format("Cannot determine path ('{0}'). See $Error[0].Exception.InnerException.InnerException for more details.",e.Message), e);
}
return results;
}
}
'@
##############################
#### How to use the attribute:
#### Just decorate your parameter, and Powershell will resolve wildcards for you and everything!
#### E.g.: try this: cd hklm:\software; get-path M*
function Get-Path {
param(
[Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("PSPath")][String[]]
[ResolvePaths()]
$Path
)
process { $Path }
}
function Get-DrivePath {
param(
[Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]][ResolvePaths(ResolveAs="Drive")]
[Alias("PSPath")]$Path
)
process { $Path }
}
function Get-ProviderPath {
param(
[Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]][ResolvePaths(ResolveAs="Provider")]
[Alias("PSPath")]$Path
)
process { $Path }
}
function Get-RelativePath {
param(
[Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]][ResolvePaths(ResolveAs="Relative")]
[Alias("PSPath")]$Path
)
process { $Path }
}
function Copy-WhatIf {
param(
[Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("PSPath")][String[]]
[ResolvePaths()]
$Source,
[Parameter(Position=1,Mandatory=$true)]
[String]
[ResolvePaths()]
$Destination
)
process {
if(!(Test-Path $Destination)) {
mkdir $Destination -whatif
}
Copy-Item $Source $Destination -whatif
}
}
#########################
#### A TON OF TEST CASES
function Assert-Equal {
Param($expected,[scriptblock]$code,$errorMessage)
$output = &$code
if( $expected -is [scriptblock]) {
$expected = &$expected
}
if( $Expected -is [Array]) {
if( compare-object $expected $output ) {
Write-Warning ("Expected '$expected', but got '$output'`n+`t" + $MyInvocation.PositionMessage)
}
} else {
if( $expected -ne $output ) {
Write-Warning ("Expected '$expected', but got '$output'`n+`t" + $MyInvocation.PositionMessage)
}
}
}
Push-Location C:\users\ | out-null
## Run the same tests using the attribute:
cd C:\users\ | out-null
# Note: Get-Path doesn't require the path to actually EXIST!
# Paths without folders should be treated as relative, obviously
Assert-Equal "C:\NotAUser" { get-path C:\NotAUser }
Assert-Equal "C:\users\NotAUser" { get-path C:\users\NotAUser }
Assert-Equal "C:\users\NotAUser" { get-path C:NotAUser }
Assert-Equal "C:\users\NotAUser" { get-path NotAUser }
Assert-Equal "FileSystem::C:\NotAUser" { Get-ProviderPath C:\NotAUser }
Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath C:\users\NotAUser }
Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath C:NotAUser }
Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath NotAUser }
Assert-Equal "..\NotAUser" { Get-RelativePath C:\NotAUser }
Assert-Equal ".\NotAUser" { Get-RelativePath C:\users\NotAUser }
Assert-Equal ".\NotAUser" { Get-RelativePath C:NotAUser }
Assert-Equal ".\NotAUser" { Get-RelativePath NotAUser }
# Convert-Path DOES require the path to exist
# Get-Path should behave the same as Convert-Path, as long as the paths exist
Assert-Equal { convert-path Public } { get-path Public }
Assert-Equal { convert-path C:\users\Public } { get-path C:\users\Public }
Assert-Equal { convert-path C:Public } { get-path C:Public }
# Including supporting wildcards:
Assert-Equal { convert-path Public\* } { get-path Public\* }
Assert-Equal { convert-path C:\*\Public } { get-path C:\*\Public }
Assert-Equal { Resolve-Path Public -Relative } { Get-RelativePath Public }
Assert-Equal { Resolve-Path C:\users\Public -Relative } { Get-RelativePath C:\users\Public }
Assert-Equal { Resolve-Path C:Public -Relative } { Get-RelativePath C:Public }
# Including supporting wildcards:
Assert-Equal { Resolve-Path Public\* -Relative } { Get-RelativePath Public\* }
Assert-Equal { Resolve-Path C:\*\Public -Relative } { Get-RelativePath C:\*\Public }
# The attribute will not work on paths that have wildcards and can't be resolved
# Since AppData is hidden, this doesn't work:
## Assert-Equal { convert-path C:*\AppData } { get-path C:*\AppData }
Assert-Equal { convert-path C:*\Documents } { get-path C:*\Documents }
# Make sure collections work
Assert-Equal { convert-path C:\Users\*\Documents, C:\Users\*\Desktop } { get-path C:\Users\*\Documents, C:\Users\*\Desktop }
cd hklm:\software\microsoft | out-null
Assert-Equal "FileSystem::C:\Test" { Get-ProviderPath C:\Test }
Assert-Equal "FileSystem::C:\users\Test" { Get-ProviderPath C:\users\Test }
Assert-Equal "FileSystem::C:\users\Test" { Get-ProviderPath C:Test }
## Nothing will normally convince other providers to include a drive name
Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft" { get-path hklm:\software\microsoft }
Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Windows" { get-path Windows }
Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Windows" { get-path hklm:Windows }
Assert-Equal ".\" { Get-RelativePath hklm:\software\microsoft }
Assert-Equal ".\Windows" { Get-RelativePath Windows }
Assert-Equal ".\Windows" { Get-RelativePath hklm:Windows }
Assert-Equal "..\Classes" { Get-RelativePath hklm:\SOFTWARE\Classes }
Assert-Equal "..\..\SYSTEM\CurrentControlSet" { Get-RelativePath hklm:\SYSTEM\CurrentControlSet }
Assert-Equal "HKEY_CURRENT_USER\Network" { Get-RelativePath hkcu:\Network }
Assert-Equal { convert-path Windows } { get-path Windows }
Assert-Equal { convert-path hklm:\software\microsoft } { get-path hklm:\software\microsoft }
Assert-Equal { convert-path hklm:Windows } { get-path hklm:Windows }
Assert-Equal "Registry::HKEY_LOCAL_MACHINE\software\microsoft\Test" { Get-ProviderPath Test }
cd variable: | out-null
Assert-Equal "Variable::Test" { Get-ProviderPath Test }
Assert-Equal "Environment::Test" { Get-ProviderPath env:\Test }
cd env: | out-null
Assert-Equal "Variable::Test" { Get-ProviderPath variable:Test }
Assert-Equal "Environment::Test" { Get-ProviderPath Test }
## But I worked out how:
cd hklm:\software\microsoft | out-null
Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Test" { get-path Test }
Assert-Equal "hklm:\software\microsoft\Test" { Get-DrivePath Test }
Assert-Equal "Registry::HKEY_LOCAL_MACHINE\software\microsoft\Test" { Get-ProviderPath Test}
cd variable: | out-null
Assert-Equal "Variable:\Test" { Get-DrivePath Test }
Assert-Equal "Env:\Test" { Get-DrivePath env:\Test }
cd env: | out-null
Assert-Equal "Variable:\Test" { Get-DrivePath variable:Test }
Assert-Equal "Env:\Test" { Get-DrivePath Test }
cd c:
if(test-path ~\Documents\WindowsPowerShell\TestData) {
cd ~\Documents\WindowsPowerShell\TestData | out-null
Assert-Equal {ls -s | convert-path } {ls -s | Get-Path}
Assert-Equal {ls -s | resolve-path -relative } {ls -s | Get-RelativePath}
}
Pop-Location