Jak poradzić sobie ze ścieżkami podczas pisania Cmdletu PowerShell?
Jaki jest właściwy sposób otrzymania pliku jako parametru podczas pisania C# cmdlet? Jak na razie mam tylko właściwość LiteralPath (wyrównującą się do ich konwencji nazewnictwa parametrów), która jest ciągiem znaków. Jest to problem, ponieważ po prostu dostajesz to, co jest wpisane do konsoli; co może być pełną ścieżką lub może być ścieżką względną.
Używanie Ścieżki.GetFullPath (string) nie działa. Myśli, że obecnie jestem w~, nie jestem. Ten sam problem występuje, jeśli zmienię właściwość z ciągu znaków na FileInfo.
EDIT: dla wszystkich zainteresowanych, To obejście działa dla mnie:
SessionState ss = new SessionState();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
LiteralPath jest parametrem łańcuchowym. Nadal jestem zainteresowany poznaniem zalecanego sposobu obsługi ścieżek plików przekazywanych jako parametry.
EDIT2: tak jest lepiej, żebyś nie zadzierał z bieżącym katalogiem users, powinieneś go ustawić z powrotem.
string current = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
Directory.SetCurrentDirectory(current);
2 answers
To zaskakująco złożona dziedzina, ale mam tu mnóstwo doświadczenia. Krótko mówiąc, są pewne cmdlety, które akceptują ścieżki win32 prosto z System.IO API, a te zazwyczaj używają parametru-FilePath. Jeśli chcesz napisać dobrze zachowujący się cmdlet "powershelly", potrzebujesz-Path i-LiteralPath, aby zaakceptować wejście potoku i pracować ze względnymi i bezwzględnymi ścieżkami dostawcy. Oto fragment postu na blogu, który napisałem jakiś czas temu:
Ścieżki w PowerShell są trudne do zrozumieć [na początku. Nie należy mylić ze ścieżkami Win32 - w ich absolutnych formach występują one w dwóch odrębnych smakach:]}
- dostawca-kwalifikowany:
FileSystem::c:\temp\foo.txt
- psdrive-Kwalifikacje:
c:\temp\foo.txt
Bardzo łatwo jest pomylić ścieżki provider-internal (właściwość ProviderPath
rozwiązanej System.Management.Automation.PathInfo
– części po prawej stronie ::
ścieżki kwalifikowanej przez provider powyżej) i drive-qualified (ścieżki kwalifikowane przez provider), ponieważ wyglądają tak samo, jeśli spójrz na domyślne dyski dostawcy systemu plików. Oznacza to, że dysk PSDrive ma tę samą nazwę (C) co macierzysty magazyn kopii zapasowych, system plików windows (C). Aby ułatwić sobie zrozumienie różnic, stwórz sobie nowy napęd PSDrive:]}
ps c:\> new-psdrive temp filesystem c:\temp\
ps c:\> cd temp:
ps temp:\>
Spójrzmy na to jeszcze raz:
- dostawca-kwalifikowany:
FileSystem::c:\temp\foo.txt
- drive-qualified:
temp:\foo.txt
Trochę łatwiej tym razem zobaczyć, co jest INNE tym razem. Pogrubiony tekst po prawej stronie nazwa dostawcy to ścieżka dostawcy.
Tak więc, Twoje cele do napisania Generalized provider-friendly Cmdlet (lub zaawansowanej funkcji), która akceptuje ścieżki, są następujące:]}- Define a
LiteralPath
path parameter aliased toPSPath
- Zdefiniuj parametr
Path
(który rozwiąże symbole wieloznaczne / glob)
Win32 paths)
$null
zawsze zakładaj, że otrzymujesz PSPaths, a nie natywne provider-paths (np.]}
Punkt numer trzy jest szczególnie ważny. Również, oczywiście LiteralPath
i Path
powinny należeć do wzajemnie wykluczających się zestawów parametrów.
Ścieżki Względne
Dobre pytanie brzmi: jak radzimy sobie ze ścieżkami względnymi przekazywanymi do Cmdletu. Ponieważ powinieneś założyć, że wszystkie ścieżki podane do ciebie są ścieżkami PSPaths, spójrzmy na to, co robi poniższy Cmdlet: {]}
ps temp:\> write-zip -literalpath foo.txt
Komenda powinna przyjąć foo.txt znajduje się w bieżącym dysku, więc powinno to zostać natychmiast rozwiązane w bloku ProcessRecord lub EndProcessing (używając API skryptów tutaj do demo):
$provider = $null;
$drive = $null
$pathHelper = $ExecutionContext.SessionState.Path
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
"foo.txt", [ref]$provider, [ref]$drive)
Teraz wszystko, czego potrzebujesz, aby odtworzyć dwie absolutne formy Pspath, a także masz natywny absolutny ProviderPath. Aby utworzyć kwalifikowaną przez dostawcę ścieżkę PSPath dla foo.txt, użyj $provider.Name + “::” + $providerPath
. Jeśli $drive
nie jest $null
(twoja obecna lokalizacja może być kwalifikowana przez dostawcę, w tym przypadku $drive
będzie $null
), powinieneś użyć $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt"
, aby uzyskać ścieżkę pspath z kwalifikacją dysku.
Quickstart C# Skeleton
Oto szkielet C# provider-aware / align = "left" / Ma wbudowane kontrole, aby upewnić się, że został przekazany dostawcy systemu plików ścieżka. Jestem w trakcie pakowania tego dla NuGet, aby pomóc innym w pisaniu dobrze wychowanych Cmdletów świadomych dostawcy: {]}
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;
}
}
}
Cmdlet Development Guidelines (Microsoft)
Oto kilka bardziej uogólnionych porad, które powinny ci pomóc w dłuższej perspektywie: http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-07 07:12:09
Oto jak możesz obsługiwać Path
wprowadzanie danych w skrypcie PowerShell cmdlet:
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 {
...
}
}
Możesz wywołać tę metodę w następujący sposób:
Z bezpośrednią ścieżką:
My-Cmdlet .
Z wieloznacznym ciągiem znaków:
My-Cmdlet *.txt
Z aktualnym plikiem:
My-Cmdlet .\PowerShell_transcript.20130714003415.txt
Ze zbiorem plików w zmiennej:
$x = Get-ChildItem *.txt
My-Cmdlet -Path $x
Lub tylko z nazwą:
My-Cmdlet -Path $x.Name
Lub wklejając zestaw plików za pomocą potoku:
$x | My-Cmdlet
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-15 16:35:05