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);
Author: pnuts, 2011-12-14

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 to PSPath
  • 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

 62
Author: x0n,
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
 6
Author: oɔɯǝɹ,
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