PoshCode Logo PowerShell Code Repository

[ResolvePaths()] by Joel Bennett 4 months ago
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/6645"></script>download | new post

A long time ago, I wrote a ResolvePaths attribute which I never really published.
This is a dump of the last copy of it that I can find, in case I never finish it.

  1. Add-Type -ErrorAction Stop <# -path $PSScriptRoot\FileSystemPath.cs #> @'
  2. using System;
  3. using System.ComponentModel;
  4. using System.Management.Automation;
  5. using System.Collections.ObjectModel;
  6. using System.Collections.Generic;
  7. using System.Text.RegularExpressions;
  8.  
  9. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
  10. public class ResolvePathsAttribute : ArgumentTransformationAttribute {
  11.  
  12.   public enum PathType { Simple, Provider, Drive, Relative }
  13.  
  14.   public PathType ResolveAs { get; set; }
  15.  
  16.   public override string ToString() {
  17.      return "[ResolvePaths(" + ((ResolveAs != PathType.Simple) ? "ResolveAs=\"" + ResolveAs + "\")]" : ")]");
  18.   }
  19.  
  20.   public override Object Transform( EngineIntrinsics engine, Object inputData) {
  21.      // standard workaround for the initial bind when pipeline data hasn't arrived
  22.       if(inputData == null) {  return null; }
  23.  
  24.       ProviderInfo provider = null;
  25.       PSDriveInfo drive = null;
  26.       Collection<string> results = new Collection<string>();
  27.       Collection<string> providerPaths = new Collection<string>();
  28.       var PSPath = engine.SessionState.Path;
  29.       var inputPaths = new Collection<string>();
  30.  
  31.       try {
  32.          // in order to not duplicate code, always treat it as an object array
  33.          var inputArray = inputData as object[];
  34.          if(inputArray == null) { inputArray = new object[]{inputData}; }
  35.  
  36.  
  37.          foreach(var input in inputArray) {
  38.             // work around ToString() problem in FileSystemInfo
  39.             var fsi = input as System.IO.FileSystemInfo;
  40.             if(fsi != null) {
  41.                inputPaths.Add(fsi.FullName);
  42.             } else {
  43.                // work around FileSystemInfo actually being a PSObject
  44.                var psO = input as System.Management.Automation.PSObject;
  45.                if(psO != null) {
  46.                   fsi = psO.BaseObject as System.IO.FileSystemInfo;
  47.                   if(fsi != null) {
  48.                      inputPaths.Add(fsi.FullName);
  49.                   } else {
  50.                      inputPaths.Add(psO.BaseObject.ToString());
  51.                   }
  52.                } else {
  53.                   inputPaths.Add(input.ToString());
  54.                }
  55.             }
  56.          }
  57.  
  58.          foreach(string inputPath in inputPaths) {
  59.  
  60.             if(WildcardPattern.ContainsWildcardCharacters(inputPath)) {
  61.                providerPaths = PSPath.GetResolvedProviderPathFromPSPath(inputPath, out provider);
  62.             } else {
  63.                providerPaths.Add(PSPath.GetUnresolvedProviderPathFromPSPath(inputPath, out provider, out drive));
  64.             }
  65.  
  66.             foreach(string path in providerPaths) {
  67.                var newPath = path;
  68.  
  69.                if(ResolveAs == PathType.Provider && !PSPath.IsProviderQualified(newPath)) {
  70.                   newPath = provider.Name + "::" + newPath;
  71.                }
  72.                else if(ResolveAs != PathType.Provider && PSPath.IsProviderQualified(newPath))
  73.                {
  74.                   newPath = Regex.Replace( newPath, Regex.Escape( provider.Name + "::" ), "");
  75.                }
  76.  
  77.                if(ResolveAs == PathType.Drive)
  78.                {
  79.                   string driveName;
  80.                   if(!PSPath.IsPSAbsolute(newPath, out driveName)) {
  81.                      if(drive == null) {
  82.                         newPath = PSPath.GetUnresolvedProviderPathFromPSPath(newPath, out provider, out drive);
  83.                      }
  84.                      if(!PSPath.IsPSAbsolute(newPath, out driveName)) {
  85.                         newPath = drive.Name + ":\\" + PSPath.NormalizeRelativePath( newPath, drive.Root );
  86.                      }
  87.                   }
  88.                } else if(ResolveAs == PathType.Relative) {
  89.                   var currentPath = PSPath.CurrentProviderLocation(provider.Name).ProviderPath.TrimEnd(new[]{'\\','/'});
  90.                   var relativePath = Regex.Replace(newPath, "^" + Regex.Escape(currentPath), "", RegexOptions.IgnoreCase);
  91.                   // Console.WriteLine("currentPath: " + currentPath + "  || relativePath: " + relativePath);
  92.                   if(relativePath != newPath) {
  93.                      newPath = ".\\" + relativePath.TrimStart(new[]{'\\'});
  94.                   } else {
  95.                      try {
  96.                         newPath = PSPath.NormalizeRelativePath(newPath, currentPath);
  97.                         // Console.WriteLine("currentPath: " + currentPath + "  || relativePath: " + relativePath + "  || newPath: " + newPath);
  98.                      } catch {
  99.                         newPath = relativePath;
  100.                      }
  101.                   }
  102.                }
  103.  
  104.                results.Add(newPath);
  105.             }
  106.          }
  107.       } catch (ArgumentTransformationMetadataException) {
  108.          throw;
  109.       } catch (Exception e) {
  110.          throw new ArgumentTransformationMetadataException(string.Format("Cannot determine path ('{0}'). See $Error[0].Exception.InnerException.InnerException for more details.",e.Message), e);
  111.       }
  112.       return results;
  113.    }
  114. }
  115. '@
  116.  
  117. ##############################
  118. #### How to use the attribute:
  119. #### Just decorate your parameter, and Powershell will resolve wildcards for you and everything!
  120. #### E.g.: try this:  cd hklm:\software; get-path M*
  121.  
  122.   function Get-Path {
  123.      param(
  124.         [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  125.         [Alias("PSPath")][String[]]
  126.         [ResolvePaths()]
  127.         $Path
  128.      )
  129.      process { $Path }
  130.   }
  131.  
  132.   function Get-DrivePath {
  133.      param(
  134.         [Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  135.         [String[]][ResolvePaths(ResolveAs="Drive")]
  136.         [Alias("PSPath")]$Path
  137.      )
  138.      process { $Path }
  139.   }
  140.  
  141.   function Get-ProviderPath {
  142.      param(
  143.         [Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  144.         [String[]][ResolvePaths(ResolveAs="Provider")]
  145.         [Alias("PSPath")]$Path
  146.      )
  147.      process { $Path }
  148.   }
  149.  
  150.   function Get-RelativePath {
  151.      param(
  152.         [Parameter(Mandatory=$true,ParameterSetName="Resolved",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  153.         [String[]][ResolvePaths(ResolveAs="Relative")]
  154.         [Alias("PSPath")]$Path
  155.      )
  156.      process { $Path }
  157.   }
  158.  
  159.  
  160.   function Copy-WhatIf {
  161.      param(
  162.         [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
  163.         [Alias("PSPath")][String[]]
  164.         [ResolvePaths()]
  165.         $Source,
  166.  
  167.         [Parameter(Position=1,Mandatory=$true)]
  168.         [String]
  169.         [ResolvePaths()]
  170.         $Destination
  171.      )
  172.      process {
  173.         if(!(Test-Path $Destination)) {
  174.            mkdir $Destination -whatif
  175.         }
  176.         Copy-Item $Source $Destination -whatif
  177.      }
  178.   }  
  179.  
  180.  
  181. #########################
  182. #### A TON OF TEST CASES
  183.  
  184.   function Assert-Equal {
  185.      Param($expected,[scriptblock]$code,$errorMessage)
  186.      $output = &$code
  187.      if( $expected -is [scriptblock]) {
  188.         $expected = &$expected
  189.      }
  190.      if( $Expected -is [Array]) {
  191.         if( compare-object $expected $output ) {
  192.            Write-Warning ("Expected '$expected', but got '$output'`n+`t" + $MyInvocation.PositionMessage)
  193.         }
  194.      } else {
  195.         if( $expected -ne $output ) {
  196.            Write-Warning ("Expected '$expected', but got '$output'`n+`t" + $MyInvocation.PositionMessage)
  197.         }
  198.      }
  199.   }
  200.  
  201.   Push-Location C:\users\ | out-null
  202.  
  203.   ## Run the same tests using the attribute:
  204.  
  205.   cd  C:\users\ | out-null
  206.  
  207.   # Note: Get-Path doesn't require the path to actually EXIST!
  208.    # Paths without folders should be treated as relative, obviously
  209.    Assert-Equal "C:\NotAUser"       { get-path C:\NotAUser }
  210.    Assert-Equal "C:\users\NotAUser" { get-path C:\users\NotAUser }
  211.    Assert-Equal "C:\users\NotAUser" { get-path C:NotAUser }
  212.    Assert-Equal "C:\users\NotAUser" { get-path NotAUser }
  213.  
  214.    Assert-Equal "FileSystem::C:\NotAUser"       { Get-ProviderPath C:\NotAUser }
  215.    Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath C:\users\NotAUser }
  216.    Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath C:NotAUser }
  217.    Assert-Equal "FileSystem::C:\users\NotAUser" { Get-ProviderPath NotAUser }
  218.  
  219.    Assert-Equal "..\NotAUser"       { Get-RelativePath C:\NotAUser }
  220.    Assert-Equal ".\NotAUser" { Get-RelativePath C:\users\NotAUser }
  221.    Assert-Equal ".\NotAUser" { Get-RelativePath C:NotAUser }
  222.    Assert-Equal ".\NotAUser" { Get-RelativePath NotAUser }
  223.  
  224.    # Convert-Path DOES require the path to exist
  225.    # Get-Path should behave the same as Convert-Path, as long as the paths exist
  226.    Assert-Equal { convert-path Public } { get-path Public }
  227.    Assert-Equal { convert-path C:\users\Public } { get-path C:\users\Public }
  228.    Assert-Equal { convert-path C:Public } { get-path C:Public }
  229.    # Including supporting wildcards:
  230.    Assert-Equal { convert-path Public\* } { get-path Public\* }
  231.    Assert-Equal { convert-path C:\*\Public } { get-path C:\*\Public }
  232.  
  233.    Assert-Equal { Resolve-Path Public -Relative } { Get-RelativePath Public }
  234.    Assert-Equal { Resolve-Path C:\users\Public -Relative } { Get-RelativePath C:\users\Public }
  235.    Assert-Equal { Resolve-Path C:Public -Relative } { Get-RelativePath C:Public }
  236.    # Including supporting wildcards:
  237.    Assert-Equal { Resolve-Path Public\* -Relative } { Get-RelativePath Public\* }
  238.    Assert-Equal { Resolve-Path C:\*\Public -Relative } { Get-RelativePath C:\*\Public }
  239.  
  240.    # The attribute will not work on paths that have wildcards and can't be resolved
  241.    # Since AppData is hidden, this doesn't work:
  242.    ## Assert-Equal { convert-path C:*\AppData } { get-path C:*\AppData }  
  243.    Assert-Equal { convert-path C:*\Documents } { get-path C:*\Documents }  
  244.    # Make sure collections work
  245.    Assert-Equal { convert-path C:\Users\*\Documents, C:\Users\*\Desktop } { get-path C:\Users\*\Documents, C:\Users\*\Desktop }
  246.  
  247.  
  248.    cd hklm:\software\microsoft | out-null
  249.  
  250.    Assert-Equal "FileSystem::C:\Test"       { Get-ProviderPath C:\Test }
  251.    Assert-Equal "FileSystem::C:\users\Test" { Get-ProviderPath C:\users\Test }
  252.    Assert-Equal "FileSystem::C:\users\Test" { Get-ProviderPath C:Test }
  253.  
  254.    ## Nothing will normally convince other providers to include a drive name
  255.    Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft" { get-path hklm:\software\microsoft }
  256.    Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Windows" { get-path Windows }
  257.    Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Windows" { get-path hklm:Windows }
  258.  
  259.  
  260.    Assert-Equal ".\" { Get-RelativePath hklm:\software\microsoft }
  261.    Assert-Equal ".\Windows" { Get-RelativePath Windows }
  262.    Assert-Equal ".\Windows" { Get-RelativePath hklm:Windows }
  263.    Assert-Equal "..\Classes" { Get-RelativePath hklm:\SOFTWARE\Classes }
  264.    Assert-Equal "..\..\SYSTEM\CurrentControlSet" { Get-RelativePath hklm:\SYSTEM\CurrentControlSet }
  265.    Assert-Equal "HKEY_CURRENT_USER\Network" { Get-RelativePath hkcu:\Network }
  266.  
  267.    Assert-Equal { convert-path Windows } { get-path Windows }
  268.    Assert-Equal { convert-path hklm:\software\microsoft } { get-path hklm:\software\microsoft }
  269.    Assert-Equal { convert-path hklm:Windows } { get-path hklm:Windows }
  270.  
  271.    Assert-Equal "Registry::HKEY_LOCAL_MACHINE\software\microsoft\Test" { Get-ProviderPath Test }
  272.  
  273.    cd variable: | out-null
  274.    Assert-Equal "Variable::Test" { Get-ProviderPath Test }
  275.    Assert-Equal "Environment::Test" { Get-ProviderPath env:\Test }
  276.    cd env: | out-null
  277.    Assert-Equal "Variable::Test" { Get-ProviderPath variable:Test }
  278.    Assert-Equal "Environment::Test" { Get-ProviderPath Test }
  279.  
  280.    ## But I worked out how:
  281.    cd hklm:\software\microsoft | out-null
  282.    Assert-Equal "HKEY_LOCAL_MACHINE\software\microsoft\Test" { get-path Test }
  283.    Assert-Equal "hklm:\software\microsoft\Test" { Get-DrivePath Test }
  284.    Assert-Equal "Registry::HKEY_LOCAL_MACHINE\software\microsoft\Test" { Get-ProviderPath Test}
  285.    cd variable: | out-null
  286.    Assert-Equal "Variable:\Test" { Get-DrivePath Test }
  287.    Assert-Equal "Env:\Test" { Get-DrivePath env:\Test }
  288.    cd env: | out-null
  289.    Assert-Equal "Variable:\Test" { Get-DrivePath variable:Test }
  290.    Assert-Equal "Env:\Test" { Get-DrivePath Test }
  291.  
  292.    cd c:
  293.    if(test-path ~\Documents\WindowsPowerShell\TestData) {
  294.       cd ~\Documents\WindowsPowerShell\TestData | out-null
  295.  
  296.       Assert-Equal {ls -s | convert-path } {ls -s | Get-Path}
  297.       Assert-Equal {ls -s | resolve-path -relative } {ls -s | Get-RelativePath}
  298.  
  299.    }
  300.  
  301.    Pop-Location

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me