Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active December 17, 2024 02:50
Show Gist options
  • Save santisq/2e33c8fa60f03951b92da850b5e4f4b1 to your computer and use it in GitHub Desktop.
Save santisq/2e33c8fa60f03951b92da850b5e4f4b1 to your computer and use it in GitHub Desktop.
Add-Type @'
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Management.Automation;
using System.Runtime.InteropServices;
using Microsoft.PowerShell.Commands;
[Cmdlet(VerbsCommon.Get, "ShortPathName")]
[OutputType(typeof(string))]
public sealed class GetShortPathNameCommand : CmdletWithPathBase
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private extern static int GetShortPathNameW(
string lpszLongPath, char[] lpszShortPath, int cchBuffer);
[Parameter(Position = 1)]
public int MaxLength { get; set; } = 500;
protected override void ProcessRecord()
{
int len;
char[] buffer = new char[MaxLength];
foreach (string path in EnumerateResolvedPaths(validateExists: false))
{
if ((len = GetShortPathNameW(path, buffer, buffer.Length)) == 0)
{
WriteError(new ErrorRecord(
new Win32Exception(Marshal.GetLastWin32Error()),
"WellSh...",
ErrorCategory.InvalidResult,
path));
continue;
}
WriteObject(new string(buffer, 0, len));
}
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class CmdletWithPathBase : PSCmdlet
{
private bool _isLiteral;
private string[] _paths = [];
[Parameter(
Position = 0,
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "Path")]
[SupportsWildcards]
[ValidateNotNullOrEmpty]
public string[] Path
{
get => _paths;
set => _paths = value;
}
[Parameter(
Mandatory = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = "LiteralPath")]
[Alias("PSPath")]
[ValidateNotNullOrEmpty]
public string[] LiteralPath
{
get => _paths;
set
{
_paths = value;
_isLiteral = true;
}
}
protected IEnumerable<string> EnumerateResolvedPaths(bool validateExists = true)
{
Collection<string> resolvedPaths;
ProviderInfo provider;
foreach (string path in _paths)
{
if (_isLiteral)
{
string resolved = SessionState.Path
.GetUnresolvedProviderPathFromPSPath(path, out provider, out _);
if (!WriteErrorIfInvalidProvider(resolved, provider)
&& !WriteErrorIfNotExists(resolved, validateExists))
{
yield return resolved;
}
continue;
}
try
{
resolvedPaths = GetResolvedProviderPathFromPSPath(path, out provider);
}
catch (Exception exception)
{
WriteError(new ErrorRecord(
exception, "ResolvePath", ErrorCategory.ObjectNotFound, path));
continue;
}
foreach (string resolvedPath in resolvedPaths)
{
if (WriteErrorIfInvalidProvider(resolvedPath, provider))
{
continue;
}
yield return resolvedPath;
}
}
}
private bool WriteErrorIfInvalidProvider(string path, ProviderInfo provider)
{
if (provider.ImplementingType == typeof(FileSystemProvider))
{
return false;
}
ArgumentException ex = new($"The resolved path '{path}' is not a FileSystem path but '{provider.Name}'.");
WriteError(new ErrorRecord(ex, "InvalidProvider", ErrorCategory.InvalidArgument, path));
return true;
}
private bool WriteErrorIfNotExists(string path, bool validateExists)
{
if (!validateExists)
{
return false;
}
if (File.Exists(path) || Directory.Exists(path))
{
return false;
}
ItemNotFoundException ex = new($"Cannot find path '{path}' because it does not exist.");
WriteError(new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, path));
return true;
}
}
'@ -PassThru -WA 0 -IgnoreWarnings | Import-Module -Assembly { $_.Assembly }
Get-ShortPathName 'C:\Program Files', 'C:\Program Files (x86)'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment