c# - ¿Cómo trato con Paths al escribir un cmdlet de PowerShell?
parameters cmdlets (2)
Así es como puede manejar la entrada de Path
acceso en un cmdlet de secuencia de comandos de PowerShell:
function My-Cmdlet {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact=''Medium'')]
Param(
# The path to the location of a file. You can also pipe a path to My-Cmdlet.
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string[]] $Path
)
Begin {
...
}
Process {
# ignore empty values
# resolve the path
# Convert it to remove provider path
foreach($curPath in ($Path | Where-Object {$_} | Resolve-Path | Convert-Path)) {
# test wether the input is a file
if(Test-Path $curPath -PathType Leaf) {
# now we have a valid path
# confirm
if ($PsCmdLet.ShouldProcess($curPath)) {
# for example
Write-Host $curPath
}
}
}
}
End {
...
}
}
Puede invocar este método de las siguientes maneras:
Con una ruta directa:
My-Cmdlet .
Con una cadena comodín:
My-Cmdlet *.txt
Con un archivo real:
My-Cmdlet ./PowerShell_transcript.20130714003415.txt
Con un conjunto de archivos en una variable:
$x = Get-ChildItem *.txt
My-Cmdlet -Path $x
O solo con el nombre:
My-Cmdlet -Path $x.Name
O al pasar el conjunto de archivos a través de la canalización:
$x | My-Cmdlet
¿Cuál es la forma correcta de recibir un archivo como parámetro al escribir un cmdlet C #? Hasta ahora solo tengo una propiedad LiteralPath (que se alinea con su convención de nomenclatura de parámetros) que es una cadena. Esto es un problema porque solo obtiene lo que está escrito en la consola; que podría ser la ruta completa o podría ser una ruta relativa.
Usar Path.GetFullPath (cadena) no funciona. Piensa que estoy actualmente en ~, no lo estoy. El mismo problema ocurre si cambio la propiedad de una cadena a un FileInfo.
EDITAR: Para cualquier persona interesada, esta solución alternativa está funcionando para mí:
SessionState ss = new SessionState();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
LiteralPath es el parámetro de cadena. Todavía estoy interesado en aprender cuál es la forma recomendada de manejar las rutas de archivos que se pasan como parámetros.
EDIT2: Esto es mejor, para que no te metas con el directorio actual de los usuarios, debes volver a configurarlo.
string current = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
Directory.SetCurrentDirectory(current);
Esta es un área sorprendentemente compleja, pero tengo mucha experiencia aquí. En resumen, hay algunos cmdlets que aceptan rutas win32 directamente desde las API de System.IO, y estas normalmente usan un parámetro -FilePath. Si desea escribir un cmdlet "powershelly" bien gestionado, necesita -Path y -LiteralPath, para aceptar la entrada de canalización y trabajar con rutas de proveedor relativas y absolutas. Aquí hay un extracto de una publicación de blog que escribí hace un tiempo:
Las rutas en PowerShell son difíciles de entender [al principio]. Las rutas de PowerShell, o PSPaths , que no deben confundirse con las rutas de Win32, en sus formas absolutas, vienen en dos sabores distintos:
- Proveedor calificado:
FileSystem::c:/temp/foo.txt
- Clasificado PSDrive:
c:/temp/foo.txt
Es muy fácil confundirse sobre el interno del proveedor (la propiedad ProviderPath
de una System.Management.Automation.PathInfo
resuelta, la parte a la derecha de ::
de la ruta calificada por el proveedor anterior) y rutas calificadas para la unidad, ya que se ven lo mismo si miras las unidades predeterminadas del proveedor de FileSystem. Es decir, el PSDrive tiene el mismo nombre (C) que el almacén de respaldo nativo, el sistema de archivos de Windows (C). Por lo tanto, para que le sea más fácil comprender las diferencias, cree un nuevo PSDrive:
ps c:/> new-psdrive temp filesystem c:/temp/
ps c:/> cd temp:
ps temp:/>
Ahora, veamos esto de nuevo:
- Proveedor calificado:
FileSystem::c:/temp/foo.txt
- Unidad calificada:
temp:/foo.txt
Es un poco más fácil esta vez para ver qué es diferente esta vez. El texto en negrita a la derecha del nombre del proveedor es ProviderPath.
Entonces, sus objetivos para escribir un Cmdlet (o función avanzada) favorable al proveedor generalizado que acepte rutas son:
- Defina un parámetro de ruta
LiteralPath
aliasPSPath
- Definir un parámetro de
Path
(que resolverá los comodines / glob) - Siempre suponga que está recibiendo PSPaths, NO rutas de proveedor nativas (por ejemplo, rutas Win32)
El punto número tres es especialmente importante. Además, obviamente, LiteralPath
y Path
deberían pertenecer a conjuntos de parámetros mutuamente excluyentes.
Caminos relativos
Una buena pregunta es: ¿cómo manejamos las rutas relativas que se pasan a un Cmdlet? Como debe suponer que todas las rutas que se le asignan son PSPath, veamos lo que hace el Cmdlet a continuación:
ps temp:/> write-zip -literalpath foo.txt
El comando debería suponer que foo.txt está en la unidad actual, por lo que debería resolverse inmediatamente en el bloque ProcessRecord o EndProcessing como (usando la API de scripting aquí para la demostración):
$provider = $null;
$drive = $null
$pathHelper = $ExecutionContext.SessionState.Path
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
"foo.txt", [ref]$provider, [ref]$drive)
Ahora tiene todo lo que necesita para recrear las dos formas absolutas de PSPaths, y también tiene el nativo ProviderPath absoluto. Para crear un PSPath calificado por el proveedor para foo.txt, use $provider.Name + “::” + $providerPath
. Si $drive
no es $null
(su ubicación actual puede ser calificada por el proveedor en cuyo caso $drive
será $null
), entonces debe usar $drive.name + ":/" + $drive.CurrentLocation + "/" + "foo.txt"
para obtener un PSPath calificado para el disco.
Quickstart C # Skeleton
A continuación, encontrará un esquema de un cmdlet compatible con el proveedor de C # para que funcione. Ha incorporado comprobaciones para garantizar que se le haya entregado una ruta de proveedor de FileSystem. Estoy en el proceso de empaquetar esto para que NuGet ayude a otros a escribir bien conocidos cmdlets de proveedores:
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
namespace PSQuickStart
{
[Cmdlet(VerbsCommon.Get, Noun,
DefaultParameterSetName = ParamSetPath,
SupportsShouldProcess = true)
]
public class GetFileMetadataCommand : PSCmdlet
{
private const string Noun = "FileMetadata";
private const string ParamSetLiteral = "Literal";
private const string ParamSetPath = "Path";
private string[] _paths;
private bool _shouldExpandWildcards;
[Parameter(
Position = 0,
Mandatory = true,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ParamSetLiteral)
]
[Alias("PSPath")]
[ValidateNotNullOrEmpty]
public string[] LiteralPath
{
get { return _paths; }
set { _paths = value; }
}
[Parameter(
Position = 0,
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ParamSetPath)
]
[ValidateNotNullOrEmpty]
public string[] Path
{
get { return _paths; }
set
{
_shouldExpandWildcards = true;
_paths = value;
}
}
protected override void ProcessRecord()
{
foreach (string path in _paths)
{
// This will hold information about the provider containing
// the items that this path string might resolve to.
ProviderInfo provider;
// This will be used by the method that processes literal paths
PSDriveInfo drive;
// this contains the paths to process for this iteration of the
// loop to resolve and optionally expand wildcards.
List<string> filePaths = new List<string>();
if (_shouldExpandWildcards)
{
// Turn *.txt into foo.txt,foo2.txt etc.
// if path is just "foo.txt," it will return unchanged.
filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider));
}
else
{
// no wildcards, so don''t try to expand any * or ? symbols.
filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
path, out provider, out drive));
}
// ensure that this path (or set of paths after wildcard expansion)
// is on the filesystem. A wildcard can never expand to span multiple
// providers.
if (IsFileSystemPath(provider, path) == false)
{
// no, so skip to next path in _paths.
continue;
}
// at this point, we have a list of paths on the filesystem.
foreach (string filePath in filePaths)
{
PSObject custom;
// If -whatif was supplied, do not perform the actions
// inside this "if" statement; only show the message.
//
// This block also supports the -confirm switch, where
// you will be asked if you want to perform the action
// "get metadata" on target: foo.txt
if (ShouldProcess(filePath, "Get Metadata"))
{
if (Directory.Exists(filePath))
{
custom = GetDirectoryCustomObject(new DirectoryInfo(filePath));
}
else
{
custom = GetFileCustomObject(new FileInfo(filePath));
}
WriteObject(custom);
}
}
}
}
private PSObject GetFileCustomObject(FileInfo file)
{
// this message will be shown if the -verbose switch is given
WriteVerbose("GetFileCustomObject " + file);
// create a custom object with a few properties
PSObject custom = new PSObject();
custom.Properties.Add(new PSNoteProperty("Size", file.Length));
custom.Properties.Add(new PSNoteProperty("Name", file.Name));
custom.Properties.Add(new PSNoteProperty("Extension", file.Extension));
return custom;
}
private PSObject GetDirectoryCustomObject(DirectoryInfo dir)
{
// this message will be shown if the -verbose switch is given
WriteVerbose("GetDirectoryCustomObject " + dir);
// create a custom object with a few properties
PSObject custom = new PSObject();
int files = dir.GetFiles().Length;
int subdirs = dir.GetDirectories().Length;
custom.Properties.Add(new PSNoteProperty("Files", files));
custom.Properties.Add(new PSNoteProperty("Subdirectories", subdirs));
custom.Properties.Add(new PSNoteProperty("Name", dir.Name));
return custom;
}
private bool IsFileSystemPath(ProviderInfo provider, string path)
{
bool isFileSystem = true;
// check that this provider is the filesystem
if (provider.ImplementingType != typeof(FileSystemProvider))
{
// create a .NET exception wrapping our error text
ArgumentException ex = new ArgumentException(path +
" does not resolve to a path on the FileSystem provider.");
// wrap this in a powershell errorrecord
ErrorRecord error = new ErrorRecord(ex, "InvalidProvider",
ErrorCategory.InvalidArgument, path);
// write a non-terminating error to pipeline
this.WriteError(error);
// tell our caller that the item was not on the filesystem
isFileSystem = false;
}
return isFileSystem;
}
}
}
Pautas de desarrollo de cmdlet (Microsoft)
Aquí hay algunos consejos más generalizados que deberían ayudarlo a largo plazo: http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx