# 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 results = new Collection(); Collection providerPaths = new Collection(); var PSPath = engine.SessionState.Path; var inputPaths = new Collection(); 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