Глава 2. Считывание и запись вывода

Содержание

Глава 2. Считывание и запись вывода
Технические требования
Работа с выводом
Приготовление
Как это сделать...
Как это работает...
Дополнительно
Сохранение данных
Как это сделать...
Как это работает...
Доступные только на чтение переменные и константы
Как это сделать...
Как это работает...
Также ознакомьтесь...
Область действия переменных
Как это сделать...
Как это работает...
Также ознакомьтесь...
Шесть потоков
Как это сделать...
Как это работает...
Также ознакомьтесь...
Перенаправление потоков
Как это сделать...
Как это работает...
Работа с поставщиком файловой системы
Приготовление
Как это сделать...
Как это работает...
Работа с поставщиком реестра
Как это сделать...
Как это работает...
Также ознакомьтесь...
Работа с поставщиком сертификатов
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Создание вашего собственного поставщика
Как это сделать...
Как это работает...
Альтернативные потоки данных NTFS
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно

Работа с выводом и данными является наиболее общим моментом, который вы будете делать в PowerShell. Начиная с выработки данных для отчётов, вплоть до быстрого просмотра состояния системы, до вставления пачкой значений в некую таблицу базы данных: всё это должно иметь дело с данными и выводом.

Мы рассмотрим в этой главе целый круг вопросов, причём на примере простых рецептов вы узнаете как получить доступ к хранилищам сертификатов, реестру в машине с Windows и к имеющейся файловой системе в любой ОС. Вы также узнаете как надлежащим образом работать с переменными.

В этой главе мы рассмотрим такие рецепты:

  • Работа с выводом

  • Сохранение данных

  • Доступные только для чтения и постоянные переменные

  • Область действия переменных

  • Шесть потоков

  • Переназначение потоков

  • Работа с поставщиком файловой системы

  • Работа с поставщиком реестра

  • Работа с поставщиком сетрификата

  • Создание вашего собственного поставщика

  • Альтернативные потоки NTFS

Технические требования

Применяемый в данной главе код можно найти в GitHub.

Работа с выводом

Этот очень простой рецепт показывает вам необходимые снасти когда вам требуется собирать обыкновенный вывод из некой локальной системы. Чтобы получать выходные данные, как правило, мы используем cmdlet с глаголом Get. Однако вы обнаружите что прочие cmdlet, которые создаю, изменяют, либо удаляют элементы, также способны возвращать данные для работы с ними.

Приготовление

Чтобы быть гтовым к началу, вам требуется иметь установленным PowerShell Core. Данный рецепт работает в любой операционной системе, способной работать с PowerShell Core.

Как это сделать...

Давайте выполним такие шаги:

  1. Откройте PowderShell

  2. Выполните Get-UICulture, а затем Get-Process. Обратите внимание что получаемый по умолчанию для этих двух cmdlet выглядит похожим на табличный, что демонстрируется ниже:

     

    Рисунок 2-1



  3. Выполните Get-TimeZone, а следом Get-Uptime. На этот раз назначенное по умолчанию форматирование вывода выглядит перечнем, как показано далее:

     

    Рисунок 2-2



  4. Теперь исполните Get-ChildItem -Path *DoesNotExist*. Для этого cmdlet вы можете и вовсе не получать вывод.

  5. Наконец, запустите New-Item -Name file -Value 'Test file'. Хотя этот cmdlet и не применяет соответствующий глагол Get, он всё ещё возвращает некий вывод для своей обработки.

Как это работает...

Когда вы выполняете некий cmdlet Get, PowerShell обычно пытается выполнить выборку данных из какого- то источника. К примеру, это могут быть запущенные в системе процессы, установленный сетевой адаптер, а также время работы данной текущей системы.

Дополнительно

  • Для получения дополнительных сведений относительно форматирования, просмотрите следующую тему справки: Get-Help about_Format.ps1xml

  • Для получения дополнительных сведений относительно типа системы, просмотрите следующую тему справки: Get-Help about_Types.ps1xml

Сохранение данных

Наличие данных в получаемом выводе может удовлетворять неких системных администраторов, однако применяя PowerShell мы бы желали взаимодействовать со своими данными. Данный рецепт покажет вам что та информация, которую вы видите на своём экране может предложить вам намного больше когда вы рассмотрите на объекты в контексте сценариев PowerShell.

Воспользовавшись переменными вы можете сохранять некую ссылку на один или более объектов для последующего доступа к ним. Это не просто удобство при интерактивной работе с командной строкой, но также может означать более быстрое исполнение сценариев, так как ваши данные не извлекаются пока они не требуются.

В этом рецепте мы обнаружим, что наблюдаемый нами результат совсем не то чем он может казаться.

Как это сделать...

Установите и откройте PowerShell Core в любой из предпочитаемых вами систем и осуществите следующие этапы:

  1. Наберите Get-Help about_Variables.

  2. для создания новых переменных выполните три следующих строки:

    
    $timestamp = Get-Date
    $processes = Get-Process
    $nothing = $null
     	   
  3. Запуск вашей переменной просто повторяет вывод:

    
    $timestamp
     	   
  4. Введите $timestamp., а затем нажмите клавишу Tab в CLI, либо Ctrl + Space в CLI и VS Code. Ознакомьтесь с различными свойствами и методами, которые тут могут применяться.

  5. Попробуйте вомпользоваться $timestamp.DayOfWeek и $timestamp.IsDaylightSavingTime().

  6. Также прекрасно работает доступ к свойствам и методам в списках. Попробуйте выполнить следующее:

    
    $processes.Name
    $processes.Refresh()
     	   
  7. Будьте внимательными при использовании не инициализированных переменных, так как результат потенциально может быть разрушительным:

    
    # Значением свойства также может быть и $null
    $nothing.SomeProperty
    
    # Вызовы метода способны вызывать ошибку
    $nothing.SomeMethod()
    
    # Особенной аккуратности требуют cmdlets подобные Get-ChildItem!
    # Значением пути по умолчанию выступает текущий рабочий каталог
    Get-ChildItem -Path $nothing -File | Remove-Item -WhatIf
     	   
  8. Наконец, вам не требуется вовсе создавать некую переменную если вам всего лишь один раз требуется получить доступ к некому свойству или методу:

    
    # Accessing properties and methods while discarding the
    # original object requires expressions, $( )
    $(Get-TimeZone).BaseUtcOffset
    $(Get-Process).ToString()
     	   

Как это работает...

PowerShell будет сохранять в ваших переменных либо получаемое значение, либо ссылку на некий объект. Подробнее типы значений и типы ссылок мы изучим в Главе 3, Работа с объектами. На данный момент вам следует запомнить сам термин нотации точки и просто пытаться применять его ко всякой обнаруженной вами переменной.

Применяя нотацию точки можно выполнять доступ к индивидуальным свойствам или методам одного или более объектов, а при помощи выражений вам даже не нужно создавать какую бы то ни было переменную. Вычисляется всё выражение целиком и полученный результат затем применяется для доступа к некому свойству или методу.

В этом рецепте вы обнаружили, что получаемый на экране отформатированный вывод не содержит все возможные свойства и методы. Всегда важно целиком зучать возвращаемые данные, например, при помощи нотации точки, чтобы понять с чем вы имеете дело.

Доступные только на чтение переменные и константы

Может так случиться, что вы пожелаете воспользоваться в своём коде доступными только для чтения или постоянными переменными. Данный рецепт покажет как создавать переменные для особых случаев, например, только для чтения или константы и зачем вам это может потребоваться. Вы также обнаружите некоторые важные автоматические переменные, которые, между прочим, также константы.

Как это сделать...

Установите и запустите PowerShell Core и выполните такие шаги:

  1. Попробуйте выполнить такой блок кода:

    
    $true = $false
    $pid = 0
    $Error = "Я никогда не ошибаюсь!"
     	   
  2. Чтобы понять почему Шаг 1 не сработал как ожидалось выполните следующий cmdlet:

    
    Get-Variable | Where-Object -Property Options -like *Constant*
     	   
  3. При помощи cmdlet variable вы имеете возможность создавать собственные доступные только для чтения и не изменяемые переменные:

    
    # Создание доступных только для чтения и не изменяемых переменных требует cmdlet переменных
    $logPath = 'D:\Logs'
    Set-Variable -Name logPath -Option ReadOnly
     	   
  4. Изменение доступных только для чтения переменных всё ещё возможно при помощи параметра -Force:

    
    Set-Variable -Name logPath -Value '/var/log' -Force
     	   
  5. Значение Option параметра также полезно и для иных целей, например, для создания частных и глобальных переменных:

    
    Get-Help -Name New-Variable -Parameter Option
     	   

Как это работает...

Cmdlet variable предоставляют вам слегка больший контроль над тем как создаются переменные, по сравнению с простым присвоением, $variable = "Значение", которое вы видели в предыдущем примере. Предоставляя параметры вы можете управлять тем где будут видны задаваемые переменные и будут ли они доступными только для чтения или константами (не изменяемыми).

Также ознакомьтесь...

Не изменяемые переменные (константы) могут определяться исключительно при их создании с посощью New-Variable и могут создаваться или удаляться лишь при создании нового сеанса PowerShell.

Область действия переменных

Области действия важни для ваших повседневных сценариев и они управляют значениями видимости ваших переменных, функций и псевдонимов. Хотя PowerShell и проделывает автоматически огромное задание по обработке сфер действия таким образом, что это ощущается очень естественным для его пользователя, в своих сценариях вы также можете применять области действия и в явном виде.

Как это сделать...

Установите и запустите PowerShell Core , а потом выполните следующие шаги:

  1. Выполните слудующий блок кода:

    
    $outerScope = 'Variable outside function'
    
    function Foo
    {
        Write-Host $outerScope
        $outerScope = 'Variable inside function'
        Write-Host $outerScope
    }
    
    Foo
    Write-Host $outerScope
     	   

    Вы должны получить такой вывод:

     

    Рисунок 2-3



  2. Слегка измените свой код, применяя модификаторы области действия private, script и local, а потом попробуйте снова:

    
    <#
      Определяя область действия в явном виде, вы можете изменять значение состояние практически любой переменной
      Доступны следующие области действия. Ко внутренним областям действия могут выполнять доступ переменные из внешних областей
      global: Самая внешняя область действия, т.е. ваш текущий сеанс
      script: Значение области действия сценария или модуля
      local: Значение области действия внутри некого блока сценария
      private: В любой области действия, скрывая от дочерних областей действия
      using: Это особый случай.
    #>
    $outerScope = 'Переменная вне function'
    $private:invisible = 'Не видимая из дочерних областей'
    
    function Foo
    {
        Write-Host $outerScope
        $script:outerScope = 'Переменная внутри function'
        $local:outerScope = 'Могут существовать обе'
        Write-Host "Доступ к содержимому переменной private не может быть осуществлён: $invisible"
        Write-Host $outerScope
    }
    
    Foo
    Write-Host $outerScope
     	   

    На этот раз вы можете заметить, что вы можете создавать множество переменных с одним и тем же самым названием в различных областях действия.

  3. Для того чтобы увидеть как работает применение области действия попробуйте далее такой код:

    
    $processName = 'pwsh'
    $credential = New-Object -TypeName pscredential -ArgumentList 'user',$('password' | ConvertTo-SecureString -AsPlainText -Force)
    Start-Job -ScriptBlock { Get-Process -Name $processName} | Wait-Job | Receive-Job # Ошибка
    Start-Job -ScriptBlock { Get-Process -Name $using:processName} | Wait-Job | Receive-Job # Работает
    Start-Job -ScriptBlock { $($using:credential).GetNetworkCredential().Password } | Wait-Job | Receive-Job # Также работает
     	   

    Как вы можете видеть из вывода предыдущего cmdlet, применение области действия делает возможным доступ к переменным изнутри некого блока сценария.

Как это работает...

PowerShell обычно автоматически обрабатывает области действия переменной. При вашей интерактивной раьоте в CLI вы автоматически применяете его глобальную область действия. Вызов некого сценария исполнит этот сценарий в области действия этого сценария. Обычно исполнение функции или некого блока сценария запускает их в некой иной области действия, а именно, в локальной области.

Приявном применении модификаторов области действия вы можете изменять переменные, псевдонимы и функции к которым обычно вы не можете выполнять доступ при текущем положении. Хотя это и позволяет вам работать вне пределов ваших обычных ограничений, вам следует быть внимательным и не перекрывать имеющийся код своих пользователей используя имеющуюся глобальную область в большей степени.

Также ознакомьтесь...

Когда вы работаете с ними не внимательно, например, используя точку как источник, области действия могут создавать некие проблемы. Применяя в качестве источника точку в . 'Full Script Path.ps1' будет означать, что весь этот сценарий целиком вместе со всем своим содержимым будет импортирован в вашу текущую облать действия. Однако это также означает и то, что данный сценарий способен перекурывать ваши переменные, псевдонимы, функции, cmdlet и тому подобное.

Шесть потоков

PowerShell кардинально изменил сам способ автоматизации, а именно, вместо того чтобы предоставлять нам коды выхода, к которым многие привыкли. Вместо этого PowerShell применяет совершенно разные потоки для передачи вывода, ошибок, предупреждений и многого иного. Освоение таких потоков позволяет вам писать более мощные и универсальные сценарии.

Работа с шестью потоками оказывается настолько общей, что cmdlet поддерживают так называемые общие параметры, которые совместно применяются всеми cmdlet. В данном рецепте вы будете работать с такими потоками как Output, Error, Warning, Verbose, Debug и Information.

Приготовление

Установите и запустите PowerShell Core, а затем выполните следующие шаги:

  1. Испробуйте следующие cmdlet и просмотрите полученный результат:

    
    # Данная команда вернёт как ошибку, так и один объект вывода
    Get-Item -Path $home,'doesnotexist'
    
    # Обычно данная команда ничего не возвращает. Параметр Verbose однако включает другой поток.
    Remove-Item -Path $(New-TemporaryFile) -Verbose
     	   
  2. Параметр Verbose на самом деле был частью общих параметров. Выполните следующие строки и посмотрите что произойдёт теперь:

    
    Get-Item -Path $home,'doesnotexist' -ErrorAction SilentlyContinue -OutVariable file -ErrorVariable itemError
    $file.FullName # Да, это ваш файл
    $itemError.Exception.GetType().FullName # Это точно выглядит как ваша ошибка
     	   
  3. Наравне с потоками Verbose и Debug, по умолчанию поток Information невидим. Попробуйте выполнить наш следующий код чтобы увидеть универсальность такого информационного потока.

    
    function Get-AllTheInfo
    {
        [CmdletBinding()]param()
    
        Write-Information -MessageData $(Get-Date) -Tags Dev,CIPipelineBuild
        if ($(Get-Date).DayOfWeek -notin 'Saturday','Sunday')
        {
            Write-Information -MessageData "Get to work, you slacker!" -Tags Encouragement,Automation
        }
    }
    
    # Как и потоки Verbose с Debug, Information по умолчанию не виден
    # Раьота с этой информацией намного улучшается теми cmdlet которые в действительности обрабатывают имеющиеся теги
    Get-AllTheInfo -InformationVariable infos
    Get-AllTheInfo -InformationAction Continue
    
    # Информация может фильтроваться и обрабкатываться, делая сообщения ваших сценариев более выразительными
    $infos | Where-Object -Property Tags -contains 'CIPipelineBuild'
     	   
  4. Потоками также можно управлять глобально через переменные Preference, как это показано в примере ниже:

    
    $ErrorActionPreference = 'SilentlyContinue'
    $VerbosePreference = 'Continue'
    Import-Module -Name Microsoft.PowerShell.Management -Force
    Get-Item -Path '/somewhere/over/the/rainbow'
     	   
  5. С целью смягчения риска имеются дополнительные параметры. Вы можете быть знакомы с ними в качестве WhatIf и Confirm. Взгляните на следующий пример:

    
    Remove-Item -Path (New-TemporaryFile) -Confirm
    Remove-Item -Path (New-TemporaryFile) -WhatIf
    
    # Автоматическими переменными также обслуживаются WhatIf и Confirm
    $WhatIfPreference = $true
    New-TemporaryFile
    
    $WhatIfPreference = $false # Сброс в установленное по умолчанию значение
    $ConfirmPreference = 'Low' # None,Low,Medium,High
    New-TemporaryFile
     	   
  6. Для записи в различные потоки в качестве разработчика попробуйте применять cmdlet Write (за исключением Write-Host):

    
    Write-Warning -Message 'Предостережение выглядит так'
    Write-Error -Message 'В то время как ошибка представляется так'
    Write-Verbose -Message 'Verbose, Debug и Information по умолчанию скрыты'
     	   

Как это сделать...

При выполнении некого cmdlet, PowerShell автоматически применяет различные потоки для передачи сведений. Обычно разработчики применяют эти потоки. Для некого cmdlet сценария это означает использование cmdlet Write. Возможность управления неким потоком предоставляется посредством применения общих параметров.

В отличии от от управления самой доступности некого потока, весь поток целиком также может сохраняться в некой переменной прежде чем он будет продолжен через соответствующий конвейер.

Основная причина того почему мы не применяем Write-Host при разработке какого- то сценария состоит в том, что cmdlet Write-Host не выполняет запись ни в один из имеющихся потоков. Вместо этого для отображения текста используется консоль его хоста. Основная проблема такого поведения состоит в том, что его пользователь не имеет контроля над видимостью таких сообщений хоста.

Также ознакомьтесь...

Если вы желаете добавить свои собственные параметры WhatIf и Confirm, вам нет нужды реализовывать их как обычные параметры, как это показано ниже:


# Не делайте этого
function Test-WrongRiskMitigation
{
    param
    (
        [switch]
        $WhatIf
    )

    if ($WhatIf)
    {
        Write-Host "Имитация уничтожения улик"
    }
}
		

Вместо этого вы можете воспользоваться атрибутом CmdletBinding, котоый автоматически делает доступными все общие параметры и способен добавить как надлежит WhatIf и Confirm:


function Test-RiskMitigation
{
 [CmdletBinding(SupportsShouldProcess)]
 param ( )

 if ($PSCmdlet.ShouldProcess('Целевой объект, здесь: Evidence - Улика','Дейвствие, у нас: Shred - Угичтожить'))
 {
 Write-Host -ForegroundColor Red -Object 'Уничтожаем улики...'
 }
}

Test-RiskMitigation -WhatIf
Test-RiskMitigation
		

Таким образом вы можете просто реагировать на тот cmdlet, который отправляет сообщение, в качестве пользователя. Не требуется никакой дополнительной реализации кроме применения привязки этого cmdlet и автоматической переменной PSCmdlet.

Перенаправление потоков

Хотя оно и не применяется очень часто в PowerShell, время от времени для различных целей применяется перенаправление. В частности, внешние приложения имеют тенденцию путать имеющиеся потоки ошибок и вывода. Такое препятствие можно достаточно просто преодолевать при помощи перенаправления потока.

В этом рецепте вы изучите номера потоков. Все снабжённые номерами потоки могут перенаправляться при помощи операторов перенаправления, > и >>. Данный рецепт покажет как и когда применять такие операторы перенаправления вместо таких параметров как ErrorAction или Verbose.

Приготовление

Установите и запустите PowerShell Core, а потом осуществите такие шаги:

  1. Переназначать можно все потоки, тем самым создавая некий новый файл как в таком примере:

    
    Get-Item -Path $home,'nonexistant' 2> error.txt 1> success.txt
    Get-Content -Path error.txt, success.txt
     	   
  2. Вы также можете добавлять различные потоки в конец файлов применяя соответствующий оператор >>:

    
    Get-Item -Path $home,'nonexistant' 2>> error.txt 1>> success.txt
    Get-Content -Path error.txt, success.txt
     	   
  3. Потоки также можно сочетать в общий вывод при помощи оператора >&1:

    
    # Это, к примеру, помогает при плохо ведущих себя внешних приложениях
    Get-Module -Verbose -List -Name 'PackageManagement','nonexistant' 2>&1 4>&1
     	   
  4. Однако сочетание потоков в общем выводе переполнит такой вывод:

    
    $modules = Get-Module -Verbose -List -Name 'PackageManagement','nonexistant' 2>&1 4>&1
    $modules.Count # Это должно содержать лишь один...
    $modules[0] # Это несомненно не выглядит как модуль
     	   

Как это сделать...

Перенаправление потоков работает совершенно иначе чем те параметры Variable, которые вы наблюдали в нашем предыдущем рецепте. Перенаправление, в том виде как оно реализовано во многих операционных системах, будет перенаправлять некий поток, такой как Error, либо в некий файл, либо в имеющийся поток вывода. Вряд ли это можно с пользой применять как- то за исключением случая с приложениями, которые неверно выдают потоки.

Как вы наблюдали на Шаге 4, применение переназначения потока в прочие потоки следует применять аккуратно. Получаемый вывод нашего cmdlet Get-Module был переполнен некими записями об ошибках. Это совершенно другой тип данных и он несомненно помешает вам.

Работа с поставщиком файловой системы

PowerShell расширятся различными поставщиками, которые делают возможным доступ к различным хранилищам данных. Наиболее повсеестным, тем не менее, выступает поставщик файловой системы. Мы автоматически применяем его во многих сценариях и при интерактивной работе в командной строке.

Даный рецепт представляет поставщиков PowerShell и показывает вам что может делать, а чего нет поставщик файловой системы.

Приготовление

Установите и запустите PowerShell Core. Если вы хотите отображать совместный ресурс CIFS, убедитесь что вы имеете возможность подключения к нему.

Как это сделать...

Давайте выполним следующие шаги:

  1. Как только вы запустите PowerShell Core, имеющийся поставщик файловой системы начинает работать и импортирует ваши смонтированные устройства. Обычно вас помещают в ваш собственный каталог home или - когда вы системный администратор - в корне вашей системы.

  2. Взгляните на вывод Get-PSProvider чтобы посмотреть на всех доступных поставщиков, как это показано ниже:

     

    Рисунок 2-4



  3. Для просмотра того, какие cmdlet поддерживаются поставщиками, выполните следующий код:

    
    # те cmdlet, которые работают с поставщиками
    Get-Command -Noun Location, Item, ItemProperty, ChildItem, Content, Path
     	   
  4. По умолчанию различные поставщики соответствуют различным устройствам. Посмотрите на некоторых из них, выполнив такой код:

    
    # Поставщики обычно автоматически монтируют свои устройства
    Get-PSDrive
     	   
  5. Поставщики делают возможным для вас применение тех же самых основных операций, например, Set-Location по любому пути. Попробуйте это в любой имеющейся файловой системе:

    
    # Перемещение по вашей файловой системе
    Set-Location -Path $home
     	   
  6. Для конкретного поставщика файловой системы cmdlet Get-ChildItem может применять дополнительные параметры. Давайте взглянем на некоторые из них в своём следующем примере:

    
    # Параметры File и FollowSymlink доступны только в случае примененеия поставщика файловой системы
    Get-ChildItem -Recurse -File -FollowSymlink
    Get-ChildItem -Path env: # Only default parameters here
    
    # Замена универсальными символами поддерживается вне зависимости от файловой системы и поставщика
    Get-ChildItem -Path /etc/*ssh*/*config
    Get-ChildItem -Path C:\Windows\*.dll
    Get-ChildItem -Path env:\*module*
     	   
  7. Для начала заслуживает особенного рассмотрения имеющийся синтаксис для строения cmdlet Get-Command. Взгляните на следующий кодовый блок:

    
    # Рассмотрите следующий синтаксис упрощения операций,
    # например, создание множества элементов из некого массива
    $folders = @(
        "$home/test1"
        "$home/test2/sub1/sub2"
        "$home/test3"
    )
    New-Item -Path $folders -ItemType Directory -Force
    
    # либо создание некого файла во множестве местоположений
    New-Item -Path $folders -Name 'someconfig.ini' -ItemType File -Value 'key = value'
     	   
  8. Поставщик файловой системы ведёт себя аналогично обычным инструментам управления файловой системой, но ваши результаты могут отличаться, к примеру, от получаемого вывода окна свойств Windows Explorer с большим числом файлов по сравнению с Get-ChildItem:

    
    New-Item $home\hidden\testfile,$home\hidden\.hiddentestfile -ItemType File -Force
    $(Get-Item $home\hidden\.hiddentestfile -Force).Attributes = [System.IO.FileAttributes]::Hidden
    Get-ChildItem -Path $home\hidden # .hiddentestfile не появится
    Get-ChildItem -Path $home\hidden -Hidden # Покажет лишь скрытый файл
    Get-ChildItem -Path $home\hidden -Force # Возвратит все файлы
     	   
  9. Параметры Include и Exclude могут оказаться мощными опциями фильтрации для Get-ChildItem:

    
    # Параметры Include и Exclude могут служить полезными фильтрами
    # / в установках Windows по умолчанию для системных устройств
    # Делая возможной более сложные филтрации чем обычный параметр Filter
    Get-ChildItem -Path $pshome -Recurse -Include *.dll,*.json -Exclude deps.ps1
     	   
  10. cmdlet content позволяют что- нибудь изменять. Для определённой файловой системы вы как правило используете их для считывания и измененеия содержимого файла:

    
    # Это также работает и для прочих cmdlet поставщиков
    Get-Content -Path $pshome/Modules/PackageManagement/* -Include *.psm1
    Set-Content -Path $home\testfile -Value "File content`nMultiple lines"
    Add-Content -Path $home\testfile -Value 'Another new line'
    Get-Content -Path $home\testfile
     	   
  11. Наконец, выполните следующий код для отображения нового совместного ресурса CIFS с помощью PowerShell:

    
    # Именно поставщик файловой системы является тем, что позволяет монтировать дополнительные устройства поставщика
    New-PSDrive -Name onlyInShell -Root \\someserver\someshare -PSProvider FileSystem
    Get-ChildItem -Path onlyInShell:
    Remove-PSDrive -Name onlyInShell
     	   

Как это работает...

В зависимости от вашей операционной системы будут иметься такие поставщики:

  • Alias: Делает возможным доступ к псевдонимам

  • Environment: Позволяет выполнять доступ к переменным среды

  • Function: Разрешает доступ к блокам функций сценария

  • Variable: Предлагает доступ к переменным

  • FileSystem: Делает возможным доступ к самой файловой системе и имеющимся совместным ресурсам CIFS

  • Certificate: На данный момент только для Windows позволяет выполнять доступ к хранилищам сертификатов

  • Registry: Исключительно для Windows открывает доступ к имеющемуся реестру Windows

  • WSMan: В настоящий момент лишь в Windows позволяет выполнять доступ к конфигурации WinRM через WSMan

Это далеко не исчерпывающий перечень, так как новые поставщики добавляются посредством модулей или могут создаваться при помощи соответствующего модуля SHiPS, который мы рассмотрим в своём рецепте Создание вашего собственного поставщика.

Имеющийся у поставщика код реализует различные cmdlet, такие как Get-ChildItem и применяет свою собственную логику в этом коде для того чтобы выполнять то, что подразумевает соответствующее название cmdlet. Не все поставщики реализуют все операции. Хотя будет совершенно исключительным применение New-Item в файловой системе, он работает совершенно иначе в поставщике сертификата, который всего лишь позволяет создавать новые хранилища сертификатов.

Работа с поставщиком реестра

Поставщик реестра является исключеительно поставщиком Windows, который делает возможным доступ к локальному реестру некого хоста. При помощи обычных cmdlet поставщика можно перемещаться по всем локальным ульям как и по некой файловой системе.

Как это сделать...

Установите и запустите PowerShell Core в Windows, а затем выполните такие шаги:

  1. Для перечисления всех элементов в определённом улье реестра своей локальной машины выполните такой код:

    
    # Как и для файловой системы, имеется возможность обхода улья локального реестра.
    # Применяются ACL, поэтому нередки ошибки AccessDenied
    Get-ChildItem HKLM:\SOFTWARE
     	   
  2. Поскольку нет никаких дополнительных фильтров, у вас нет особого контроля над Get-ChildItem, который лишь возвращает ключи реестра и отображает их значения. Попытка перечисления значений следующим обазом завершится отказом:

    
    # По умолчанию Get-ChildItem возвращает ключи и их значения
    Get-ChildItem -Recurse -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
     	   
  3. Для перечисления значений реестра применяется cmdlet Get-ItemProperty. Попробуйте такой образец кода:

    
    # Для выборки только свойств вместо этого применяется Get-ItemProperty
    # Без предоставления названия, Get-ItemProperty возвращает для данного пути все значения
    Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    
    # Когда применяется только значение соответствующего свойства
    Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ProductName
    
    # Хотя это применяется преимущественно для доступа к реестру, это также можно применять и к
    # файловой системе. Тем не менее, такой подход слишком громоздкий
    Get-ItemProperty -Path $(Get-Command -Name pwsh).Source -Name LastWriteTime
     	   
  4. Создание новых элементов рабоатет аналогично файловой системе. Обратите внимеание, что создаются ключи реестра, не значения:

    
    # Вы можете применять New-Item для создания новых ключей
    New-Item -Path HKCU:\Software -Name MyProduct
     	   
  5. Для работы со значениями прмиеняются cmdlet ItemProperty. Для того чтобы увидеть как создаются новые значения и изменяются имеющиеся, воспользуйтесь следующим образцом кода:

    
    <#
    Для создания новых значений применяйте New-ItemProperty. Значениями для PropertyType могут быть:
    String (REG_SZ): Стандартная строка
    ExpandString (REG_EXPAND_SZ): Строка с автоматическим расширением переменной среды
    Binary (REG_BINARY): Двоичные данные
    DWord (REG_DWORD): 32bit ldjbxyjt xbckj
    MultiString (REG_MULTI_SZ): Массив строк
    QWord (REG_QWORD): 64bit двоичное число
    #>
    New-ItemProperty -Path HKCU:\Software\MyProduct -Name Version -Value '0.9.9-rc1' -PropertyType String
    New-ItemProperty -Path HKCU:\Software\MyProduct -Name SourceCode -Value $([Text.Encoding]::Unicode.GetBytes('Write-Host "Cool, isnt it?"')) -PropertyType Binary
    
    # Проверьте это ;)
    [scriptblock]::Create($([Text.Encoding]::Unicode.GetString($(Get-ItemPropertyValue -Path HKCU:\Software\MyProduct -Name SourceCode)))).Invoke()
    
    # Измените некий элемент
    Set-ItemProperty -Path HKCU:\Software\MyProduct -Name SourceCode -Value $([Text.Encoding]::Unicode.GetBytes('Stop-Computer -WhatIf'))
    [Text.Encoding]::Unicode.GetString($(Get-ItemPropertyValue -Path HKCU:\Software\MyProduct -Name SourceCode))
     	   
  6. Удалять элементы просто. Для того чтобы снова удалить ваш ключ попробуйте следующий образец кода:

    
    # Имеющийся по умолчанию cmdlet удаления работает также
    Remove-Item -Path HKCU:\Software\MyProduct -Verbose
     	   
  7. Имейте в виду, что имеющийся поставщик реестра не способен отображать удалённые реестры - чтобы иметь возможность выполнять подобное вам требуется применять .NET.

    
    # Нет возможности применять права доступа
    Get-PSProvider -PSProvider Registry
    
    # Отображение локального улья работает отлично
    New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT
    Get-ChildItem -Path HKCR:
    Remove-PSDrive -Name HKCR
     	   

Как это работает...

Как и поставщик файловой системы, поставщик реестра реализует определённые cmdlet поставщика. Это делает возможным для вас применение взаимодействовать с реестром из своих сценариев. Для обхода проблемы с невозможностью отображения улья удалённого реестра можно применять cmdlet Invoke-Command.

В то время как ваша файловая система работает с файлами и папками, имеющийся реестр работает с ключами и значениями. Такой реестр работает с ключами и значениями. Данная структура может выглядеть похожей на файловую систему, однако реализуется слегка иначе. Для доступа к к файлам вам это может проделать любой cmdlet Item. Для доступа к значениям реестра cmdlet Item несомненно рабоатют, однако для выборки только содержимого некого значения вам потребуется применять cmdlet ItemProperty.

Также ознакомьтесь...

Хотя Invoke-Command через управление веб службой и удалённое управление Windows должны быть предпочтительным способом доступа к ключам удалённого реестра, могут возникать проблемы с упорядочением и иные сложности. Применяя .NET вы также можете просто делать доступ к улью удалённого реестра через DCOM (Distributed Component Object Model) и RPC (Remote Procedure Calls) из своего локального сеанса. Для начала рассмотрите такой образец кода:


$remoteKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', 'MyHost', 'Registry64')
$remoteUserKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('CurrentUser', 'MyHost', 'Registry64')
		

Наши предыдущие строки открывают два удалённых улья, HKLM и HKCU. В наших последующих строках вы можете получить значения имён в неком ключе и получить некое определённое значение из названного ключа:


$remoteKey.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion').GetValueNames()
$remoteKey.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion').GetValue('ProductName')
		

Для создания удалённых подчинённых ключей и удалённых значений вы можете воспользоваться приводимыми ниже строками. Обратите внимание на Булево значение параметра для нашего метода OpenSubKey. Для доступа на запись требуется открыть ключ, так как по умолчанию он доступен лишь для чтения:


$remoteUserKey.OpenSubKey('SOFTWARE',$true).CreateSubKey('MyProduct').SetValue('Version','1.2.3.4', 'String')
$remoteUserKey.OpenSubKey('SOFTWARE\MyProduct').GetValue('Version')
$remoteUserKey.DeleteSubKeyTree('Software\MyProduct')
		

Хотя это и не предписано как обязательное действие, вам следует всегда высвобождать ссылки на объекты .NET чтобы процесс сборки мусора мог очищать ресурсы:


$remoteKey.Dispose()
$remoteUserKey.Dispose()
		

Работа с поставщиком сертификатов

Как и поставщик реестра, поставщик сертификатов является привязанным исключительно к Windows. Он позволяет вам выполнять доступ к хранилищам сертификатов для учётых записей компьютера илм пользователяпричём вам доступны некоторые дополнительные методы фильтрации.

Как и для поставщика реестра, есть ряд вопросов для рассмотрения.

Как это сделать...

Установите в неком хосте Windows PowerShell Core и выполните такую последовательность шагов:

  1. Для перечисления хранилищ сертификатов и самих сертификатов внутри хранилища воспользуйтесь таким cmdlet поставщика:

    
    # Ещё один поставщик, применяемый исключительно для Windows, делающий возможным доступ к локальным хранилищам сертификатов
    Get-PSProvider -PSProvider Certificate
    
    # И вновь применим установленный по умолчанию cmdlets
    # Перечисли все хранилища сертификатов
    Get-ChildItem -Path Cert:\CurrentUser
    
    # Перечилим все сертификаты конкретного персонального хранилища пользователя
    Get-ChildItem -Path Cert:\CurrentUser\my
     	   
  2. ДРасширяющие параметры cmdlet Get-ChildItem позволяют прмменять дополнительнные фильтры:

    
    # Те параметры, которые предлагаются в Windows PowerShell его потсавщиком сертификатов очень интересны,
    # будут доступны дополнительные параметры, такие как -EKU -SslServerAuthentication
    Get-ChildItem -Path Cert:\CurrentUser\my -CodeSigningCert
     	   
  3. В PowerShell Core могут использоваться не все параметры Get-ChildItem, с которыми вы знакомы в PowerShell Windows. Where-Object продолжает оставаться вашим другом:

    
    $certificate = Get-ChildItem -Path Cert:\CurrentUser\my | Select-Object -First 1
    
    # Фильтруем по значениям OID. Если невозможно разрешить OID, вместо дружественного названия применяйте ID пречисления объектов!
    # Значение OID является более надёжным и не выступает предметом локализации
    $certificate.EnhancedKeyUsageList
    
    # в качестве примера поиска по всем сертификатам аутентификации клиентов
    Get-ChildItem -Path cert:\currentuser\my | Where-Object -FilterScript {$_.EnhancedKeyUsageList.ObjectId -eq '1.3.6.1.5.5.7.3.2'}
    
    # Не менее важно; фильтрация сертификатов когда доступны частные ключи, т.е. для документов с цифровой подписью
    Get-ChildItem -path Cert:\CurrentUser\my | 
        Where-Object -Property HasPrivateKey | 
        Format-table -Property Subject,Thumbprint,@{Label='EKU'; Expression = {$_.EnhancedKeyUsageList.FriendlyName -join ','}}
    
    $certificate.HasPrivateKey
     	   
  4. Не все cmdlet Item реализованы для имеющегося поставщика сертификатов:

    
    # Хотя cmdlet New и Set не реализованы для сертификатов, Remove может применяться для некой тщательной очистки
    Get-ChildItem -Path Cert:\CurrentUser\my |
        Where-Object -Property NotAfter -lt $([datetime]::Today) |
        Remove-Item -WhatIf
     	   
  5. Однако вы всё ещё можете создавать новые хранилища сертификатов:

    
    # New-item может использоваться для новыххранилищ - но это редко применяется
    New-Item -Path Cert:\LocalMachine\NewStore
    Remove-Item -Path Cert:\LocalMachine\NewStore
     	   

Как это работает...

Как это имеет место и в случае с прочими поставщиками, имеющиеся cmdlet реализуют свою собственную функциональность и делают возможным доступ к сертификатам в вашем персональном хранилище. Хотя получаемая функциональность и достаточно ограничена, cmdlet Get-ChildItem и Get-Item полезны для фильтрации сертификатов и удаляют сертификаты с истекшим сроком если они не удалены автоматически.

Создание вашего собственного поставщика

Также весьма полезным (и увлекательным) может быть и создание вашего собственного поставщика. Соответствующий модуль SHiPS PowerShell позволяет вам устанавливать соответствие для любых имеющихся структур для доступа к ним в качестве некого устройства. Для выборки данных на данный момент SHiPS поддерживает лишь Get-Item и Get-ChildItem.

SHiPS, например, применяется в PowerShell для Оболочки Azure Cloud, а также в популярном модуле автоматизации AutomatedLab.

Как это сделать...

Установите и запустите PowerShell Core, а затем выполните такие шаги:

  1. Для получения возможности разработки своего собственного поставщика, вам требуется вначале установить из галереи SHiPS. В качестве альтернативы вы можете последовать рекомендациям в GitHub для компиляции своего собственного ядра .NET:

    
    # Установите SHiPS
    Install-Module -Name SHiPS -Force -Scope CurrentUser
     	   
  2. SHiPS основывается на классах PowerShell. Прежде всего вы можете пожелать добавить свой собственный контейнер корня:

    
    # Поставщики SHiPS основываются на классах PowerShell.
    # Для создания некого нового каталога, либо контейнера, ваш класс должен 
    # наследоваться из класса ShipsDirectory
    class MyContainerType : Microsoft.PowerShell.SHiPS.SHiPSDirectory
    {
        # Ваш новый контейнер теперь должен реализовывать по крайней мере функцию GetChildItem().
        # Чтобы в действительности возвращать дочергие элементы, ваш контейнер должен быть текущим!
        MyContainerType([string]$name): base($name)
        {
        }
    
        [object[]] GetChildItem()
        {
            $obj = @()
            $obj += [MyFirstLeafType]::new();
            return $obj;
        }
    }
     	   
  3. Добавьте другой контейнер, который вложен в ваш корень, также как и один или два листа:

    
    # Листья являются дочерними элементами ваших контейнеров, которые больше сами по себе не могут содержать никаких дополнительных элементов.
    # Тем не менее, контейнеры всё ещё могут содержать контейнеры.
    class MyFirstContainerType : Microsoft.PowerShell.SHiPS.SHiPSDirectory
    {
        MyFirstContainerType([string]$name): base($name)
        {
        }
    }
    
    class MyFirstLeafType : Microsoft.PowerShell.SHiPS.SHiPSLeaf
    {
        MyFirstLeafType () : base ("MyFirstLeafType")
        {
        }
    
        [string]$LeafProperty = 'Value'
        [int]$LeafLength = 42
        [datetime]$LeafDate = $(Get-Date)
    }
     	   
  4. Наконец, сохраните определение своего поставщика в качестве модуля и импортируйте его:

    
    # Рассмотоите данный образец кода
    psedit .\ShipsProvider.psm1
    
    # Для монтирования устройства вашего поставщика вы можете просто воспользоваться New-PSDrive
    Import-Module -Name .\ShipsProvider -Force
     	   
    [Замечание]Замечание

    psedit является удобной доступной в коде Visual Studio функцией для открытия файлов в вашем редакторе! Если вы работаете с другим IDE, для просмотра кода вам может потребоваться другая команда. Попробуйте вместо этого Get-Content.

  5. С помощью SHiPS и вашего импортированного модуля вы можете смонтировать собственное устройство. Обратите внимание, что значение корня вашего устройства полагается на значение класса root, который содержит вашу иерархию:

    
    New-PSDrive -Name MyOwnDrive -PSProvider SHiPS -Root "ShipsProvider#MyContainerType"
     	   
  6. Изучите его с помощью cmdlet item:

    
    # Теперь вы можете использоввать имеющиеся cmdlet item по своиему усмотрению
    Get-ChildItem -Path MyOwnDrive:\MyFirstContainerType
    Get-ChildItem -Path MyOwnDrive: -Recurse
     	   
  7. Обратите внимание на то как отображаются ваши листья и контейнеры:

     

    Рисунок 2-5



Как это работает...

SHiPS позволяет вам отображать доступные только для чтения структуры данных по вашему выбору, ограничиваясь лишь вашими навыками программирования. Хотя вы и не можете применять такие cmdlet как Remove-Item, вы можете определять свои собственные методы объекта или выставлять функции, которые вызывают внутренним образом cmdlet или методы .NET. Ваш собственный поставщик также может возвращать объеткы .NET из имеющихся классов, напрмиер объекты System.IO.FileInfo, возвращаемые поставщиком определённой файловой системы.

Ваши данные всё ещё могут обрабатываться cmdlet Object, в конвейерах и в противном случае обрабатываются PowerShell. Просто имейте в виду, что для выборки необходимых данных могут применяться только Get-Item и Get-ChildItem, а также что все прочие cmdlet provider-aware просто не реализованы.

Также ознакомьтесь...

Если вы уверенно себя чувствуете, вы можете добавлять своему поставщику динамические параметры. Помните параметр File для поставщика FileSystem или CodeSigningCertificate для поставщика Certificate? При помощи SHiPS вы запросто можете добавлять свои собственные параметры:


class MyDynamicParameter
{
    [Parameter()]
    [switch]
    $DispenseCandy
}

class MyContainerType : Microsoft.PowerShell.Ships.SHiPSDirectory
{
    ...
    [object] GetChildItemDynamicParameters()
    {
        return [MyDynamicParameter]::new()
    }
    ...
}

New-PSDrive -Name MyOwnDrive -PSProvider SHiPS -Root ShipsProviderAdvanced#MyContainerType
Get-ChildItem MyOwnDrive: -Recurse -DispenseCandy
		

Это не ограничивается тольао параметрами переключателей, но может быть и всеми прочими типами параметра, о которых вы только можете подумать. Это также включает также и параметры валидации, позволяющие вам, например, задавать параметр ValidateSet для Get-ChildItem и вашего собственного поставщика.

Дополнительно

Альтернативные потоки данных NTFS

Хотя он и не совсем относится к таким потокам как потоки вывода и ошибок, которые мы наблюдали до этого, существует особый вид потока, с которым достаточно интересно поиграть: альтернативный поток данных NTFS. Этот рецепт относится к особым форматированным NTFS томам и покажет вам зачем прежде всего имеется cmdlet Unblock-File.

Как это сделать...

Установите и запустите PowerShell Core, а затем выполните такое шаги:

  1. Выгрузите некий файл для упражнений:

    
    # Чтобы начать выгрузите любой файл в отформатированный NTFS том.
    # Данное лабораторное занятие предполагает что вы сохранили выгруженное в $home\Downloads
    $downloadRoot = "~\Downloads"
    
    # Выгрузите любой файл, например, некий выпуск популярной инфраструктуры даборатории автоматизации AutomatedLab
    start https://github.com/AutomatedLab/AutomatedLab/releases/download/v5.1.0.153/AutomatedLab.msi
     	   
  2. Опробуйте файл Get-Item. Вы обратили внимание на кое- какую неравномерноость? Давайте посмотрим:

    
    # На первый взгляд этот файл кажется нормальным
    Get-Item $downloadRoot\AutomatedLab.msi
     	   
  3. Давайте попробуем Get-Item снова, на этот раз с параметром -Stream. В чём теперь состоит разница? Давайте рассмотрим:

    
    Get-Item $downloadRoot\AutomatedLab.msi -Stream *
     	   

    Предыдущая строка кода отображает вам то что вы можете видеть в диалоге свойств самой файловой системы. Этот поток с названием Zone.Identifier отвечает за создание такого сообщения в вашем UI!

     

    Рисунок 2-6



    [Замечание]Замечание

    Все файлы NTFS содержат некий поток данных и дополнительные потоки, такие как значение идентификатора зоны для вызргузок файла.

  4. Потоки могут обрабатываться cmdlet-ами Item и Content:

    
    # Потоки могут обрабатываться cmdlet-ами Content-
    # Сам поток данных, конечно же, возвращается по умолчанию
    Get-Content -Path C:\windows\logs\cbs\cbs.log -Stream ':$DATA'
    
    # Хначение идентификатора зоны определяет выгружается ли данный файл из Интернета или из иной зоны
    # Из них вы можете выявлять все виды информации, например значение URL адреса в данном примере.
    # На момент написания, значение HostUrl располагалось в хранилище S3 Amazon и прменялось в качестве
    # подписи доступа для установления даты истечения срока этой ссылки
    Get-Content -Path $downloadRoot\AutomatedLab.msi -Stream Zone.Identifier
     	   
  5. Вы не только можете считывать их, вы также можете устанавливать свои собственные:

    
    # Давайте теперь испробуем другие cmdlet содержимого...
    Set-Content -Path .\TestFile -Value 'A simple file'
    $bytes = [Text.Encoding]::Unicode.GetBytes('Write-Host "Virus deployed..." -Fore Red')
    $base64script = [Convert]::ToBase64String($bytes)
    
    # Теперь внутри не привлекающего внимания файла мы скрыли некий сценарий
    Set-Content -Path .\TestFile -Stream Payload -Value $base64script
    
    # И естественно мы можем выполнить его следующей бесподобной командой в одну строку
    [scriptblock]::Create($([Text.Encoding]::Unicode.GetString($([Convert]::FromBase64String($(Get-Content .\TestFile -Stream Payload)))))).Invoke()
     	   
  6. Вы также имеете возможность очищать содержимое потока также как и удалять потоки:

    
    # Также вы можете вручную очищать определённый поток
    Clear-Content -Path .\TestFile -Stream Payload
    
    # И вы имеете возможнгость удалить этот поток целиком
    Remove-Item -Stream Payload -Path .\TestFile
     	   
  7. Для потока идентификатора зоны cmdlet Unblock-File сделает более простым удаление:

    
    # Сам cmdlet Unblock-File делает в точности то же самое со значением Zone.Identifier
    Unblock-File -Path $downloadRoot\AutomatedLab.msi
    Get-Item -Path $downloadRoot\AutomatedLab.msi -Stream *
     	   

Как это работает...

Альтернативные потоки данных NTFS, как подразумевает их название, выступают потоками, в которых могут храниться данные. Поскольку это позволяет развёртывать вредоносные программы, похожие на те, которые мы применяли в данном рецепте, такие потоки применяются чаще чемы вы полагаете.

Также ознакомьтесь...

Значение идентификатора зоны является самым простым и наиболее доступный пример, поскольку он обычно записывается при выгрузке файла обычным способом в браузере. Значения индивидуальных потоков таковы:

Таблица 1-1. Значения потоков идентификатора зоны
Идентификатор зоны Отображаемое название зоны

0

My computer

1

Local intranet

2

Trusted sites

3

Internet

4

Restricted sites

Дополнительно

Такие компоненты Windows как Диспетвер ресурсов Файлового сервера или продукты подобные DropBox также применяют альтернативные потоки данных для сохранения информации. В случае DropBox такой поток именуется com.dropbox.attributes, который, видимо, применяется для хранения данных о машине, с которой был синхронизирован этот файл, как это показано ниже:

 

Рисунок 2-7



Я надеюсь, что разжёг ваше любопытство, а потому давайте посмотрим на свои файлы чтобы обнаружить что зарыто в глубине ваших дисков:


Get-ChildItem -Path ~ -Recurse -File | Where-Object -FilterScript {
    $($_ | Get-Item -Stream *).Stream -notmatch ':\$DATA|Zone\.Identifier'
}