en-US/tutorials/Invoke-PSDriveTutorial.ps1

# a tutorial on PSProviders and PSDrives

param(
    [switch]$Full,
    [switch]$Menu
)

#region setup
$script:tutorialParam = $PSBoundParameters

if ($isWindows -or ($PSEdition -eq 'Desktop')) {
    $root = '\'
}
else {
    $root = '/'
}

$title = "$($titleStyle)Providers and PSDrives$($reset)"
if ($IsCoreCLR) {
    $psh = 'pwsh'
}
else {
    $psh = 'powershell'
}
#endregion

#region content
$intro = @'
 
For PowerShell beginners, a fundamental concept that can be mysterious at first is that of
a PowerShell {0}provider{1}. A provider is a piece of software that acts as an interface between
an underlying technology, like the Windows registry or the file system, and PowerShell. It
exposes the underlying technology to you through a {0}PSDrive{1}.
 
Unless you are a .NET and PowerShell developer, you will never create a provider, but you will
use them all of the time, even though you may not realize it.
 
PowerShell includes several providers out-of-the-box. You might get additional providers through
imported modules. For example, when you import the ActiveDirectory module, you will get a PSDrive
that acts as an interface to your Active Directory domain allowing you to navigate the domain like
a file system.
 
'@
 -f $highLight,$reset

$p1 = @"
The provider makes it possible to use the same cmdlet for different technologies. That's why the
cmdlet is called {0}Get-ChildItem{1} and not {4}Get-File{1} or {4}Get-RegistryKey{1}. In many cases, you can
navigate the technology through the PSDrive as if it were a file system.
 
The provider also makes it possible to the same cmdlet to get items from the file system or the
{2}Function{1} PSDrive. The provider handles the work.
 
"@
 -f $cmdStyle,$reset,$highLight2,$highLight3,$italic

#Get-PSProvider
$p2 = @"
To view the the providers currently in use run this command:
 
$prompt {0}Get-PSProvider{1}
"@
 -f $cmdStyle,$reset

$p3 = @"
You will see different providers depending on your operating system and depending on what modules
you have loaded that might add a provider. We'll look at {0}Drives{1} in a moment.
 
Not all providers work the same way, so even though a cmdlet might have a parameter to support a
feature, if you use the cmdlet in a PSDrive that doesn't support the feature, the cmdlet will fail.
 
The {2}ShouldProcess{1} capability refers to supporting the {3}-WhatIf{1} parameter. Although,
don't be afraid to try using it. In Windows, the {4}WSMan{1} provider looks like it doesn't support
{2}ShouldProcess{1}, yet commands like this:
 
{5}Set-Item{1} {3}-path{1} {6}WSMan:\localhost\Client\AllowUnencrypted{1} {3}-Value{1} {6}'false'{1} {3}-WhatIf{1}
 
Will actually work without error.
 
"@
 -f $highLight3,$reset,$highLight2,$paramStyle,$highLight,$cmdStyle,$stringStyle

$p4 = @"
The {0}Filter{1} capability refers to a specific filtering technique used in .NET. This is usually
exposed through the {2}-Filter{1} parameter you will see in some cmdlets. The format of the parameter
value will vary depending on the provider and cmdlet. The help should document its use.
 
If the provider doesn't support {0}Filter{1}, this just means that the {2}-Filter{1} parameter probably
won't work. However, many cmdlets have other parameters that can provide "filtering" functionality
such as {2}-Name{1},{2}-Include{1}, and {2}-Exclude{1}.
 
You shouldn't need to run {3}Get-PSProvider{1} very often except for troubleshooting or curiosity.
 
"@
 -f $highLight2,$reset,$paramStyle,$cmdStyle

#Get-PSDrive
$p5 = @"
{3}PSDrives{2}
 
Each provider will create one or more {0}PSDrives{2}. Each drive is an interface to the underlying
technology. PowerShell will create a number of PSDrives automatically when you start a new
PowerShell session. You can use {1}Get-PSDrive{2} to discover the PSDrives defined in your current
session.
 
$prompt {1}Get-PSDrive{2}
"@
 -f $highlight2,$cmdStyle,$reset,$highLight3

$p6 = @"
 
You should automatically have a PSDrive for every logical drive as seen by the operating system,
such as C:\ on Windows or \ on Linux. Although, as you'll see, the {0}FileSystem{1} PSDrives you
see here are independent of the operating system.
 
"@
 -f $highLight,$reset

#other providers
$p7 = @"
The non-filesystem drives are often of most interest to PowerShell beginners.
 
$prompt {2}Get-PSDrive{1} {3}|{1} {2}Where-Object{1} {3}{{{1}{5}`$_.Provider.Name{1} {3}-ne{1} {4}'FileSystem'{1}{3}}}{1}
"@
 -f $highLight,$reset,$cmdStyle,$operatorStyle,$stringStyle,$varStyle

$p8 = @"
You'll see different drives depending on your operating system, loaded modules, or drives that you
might have added.
 
"@


$p9 = @"
You can access these drives and possibly navigate them like a file system. Remember to use the
colon {6}:{1} after the PSDrive name.
 
$prompt {0}Get-ChildItem{1} {3}Alias:{1} {4}|{1} {0}Select-Object{1} {2}-first{1} {5}5{1}
"@
 -f $cmdStyle,$reset,$paramStyle,$defaultTokenStyle,$operatorStyle,$numberStyle,$highLight2

$p10 = @"
Many of these PSDrives are used by related cmdlets, which is what you should use.
 
$prompt {0}Get-Alias{1} {4}|{1} {0}Select-Object{1} {2}-first{1} {5}5{1}
"@
 -f $cmdStyle,$reset,$paramStyle,$defaultTokenStyle,$operatorStyle,$numberStyle

#New-PSdrive
$p11 = @"
You can create your own PSDrives to any location supported by a Provider, which you may sometimes
see referenced as a {2}PSProvider{1}. Use {0}New-PSDrive{1} to create the drive assignment. You are not
limited to using traditional drive letters. You can use any name you want, although you should avoid
names with spaces or non-alphanumeric characters.
 
$prompt {0}New-PSDrive{1} {3}-Name{1} {4}tmpHome{1} {3}-PSProvider{1} {4}FileSystem{1} {3}-Root{1} {5}`$home{1}
"@
 -f $cmdStyle,$reset,$highLight2,$paramStyle,$stringStyle,$varStyle

$p12 = @"
 
It is important to note that the operating system won't see your PSDrive. If you create a PSDrive
called {3}W{1} in Windows, that maps to C:\Work, you {5}won't see{1} a {3}W{1} drive in Windows Explorer.
The exception would be persistent PSDrives mapped to a UNC root. ({4}Read the help for {0}New-PSDrive{1}.)
Likewise, if you add a new drive mapping in Windows, there's no guarantee a corresponding PSDrive
will automatically be created in your PowerShell session.
 
Once created, you can use this location like any other path. Once again, don't forget the colon.
 
$prompt {0}Get-ChildItem{1} {2}tmpHome:{1} {7}|{1} {0}Select-Object{1} {6}-first{1} {8}3{1}
"@
 -f $cmdStyle,$reset,$defaultTokenStyle,$highLight,$italic,$warnStyle,$paramStyle,$operatorStyle,$numberStyle

#remove PSDrive
$p13 = @"
Any PSDrives that you create will only {4}last for the duration of your PowerShell session.{1} The
next time you start PowerShell, the drive won't be defined. If you have PSDrives that you want to
have every time you start PowerShell, put the {0}New-PSDrive{1} command in your PowerShell profile
script.
 
An exception to this is on Windows when you map a network drive (UNC) as a {5}persistent{1} drive.
Read the help for {0}New-PSDrive{1} to learn more.
 
If you want to remove a PSDrive from your session, use {0}Remove-PSDrive{1}. In this situation,
reference the drive name {6}without{1} the colon. As with adding a PSDrive, removing a PSDrive
is completely independent of the operating system.
 
$prompt {0}Remove-PSDrive{1} {2}-name{1} {3}tmpHome{1}
 
"@
 -f $cmdStyle,$reset,$paramStyle,$stringStyle,$warnStyle,$highLight2,$italic

#dynamic parameters
$p14 = @"
There is one other feature of PSProviders and PSDrives that you will want to take advantage of.
You now know that you can use a cmdlet like {0}Get-ChildItem{1} with different PSDrives. Some cmdlets
are designed with {2}{3}dynamic parameters{1}. These are parameters that are often provider-aware. That is,
the parameter only works with a given PSProvider. Where this can get confusing is that the help may
show the parameter, but it only works in a specific situation.
 
Here's an example. {0}Get-ChildItem{1} has a {4}-File{1} parameter.
 
$prompt {0}Get-ChildItem{1} {5}`$pshome{1} {4}-file{1} {6}|{1} {0}Select-Object{1} {4}-first{1} {7}1{1}
"@
 -f $cmdStyle,$reset,$italic,$highLight2,$paramStyle,$varStyle,$operatorStyle,$numberStyle

$p15 = @"
 
However the {2}-File{1} parameter is only available when using a location supported by the FileSystem
PSProvider. Otherwise, you will most likely get an error.
 
$prompt {0}Get-ChildItem{1} {2}Function:{1} {3}-file{1}
"@
 -f $cmdStyle,$reset,$defaultTokenStyle,$paramStyle

$p16 = @"
This is why it is very important that you take the time to read the full help and examples for
PowerShell commands.
 
"@


$p17 = @"
You can learn more about providers in general by running {0}Get-Help{1} {2}about_providers{1}. You
can also get details about a specific provider from a corresponding about topic.
 
$prompt {0}Get-Help{1} {2}about_*_provider{1}
"@
 -f $cmdStyle,$reset,$defaultTokenStyle


#endregion

#region run the tutorial

$pages = @(
    {
        Clear-Host
        $title
        $Intro
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p1
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p2
        Get-PSProvider | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p3
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p4
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p5
        Get-PSDrive | Format-Table -AutoSize | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p6
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p7
        Get-PSDrive | Where-Object {$_.Provider.Name -ne 'FileSystem'} | Format-Table -AutoSize | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p8
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p9
        Get-ChildItem Alias: | Select-Object -first 5 | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p10
        Get-Alias | Select-Object -first 5 | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p11
        if (Test-Path tmpHome:) { Remove-PSDrive tmpHome}
        New-PSDrive -Name tmpHome -PSProvider FileSystem -Root $home -Scope Script | Format-Table -AutoSize | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p12
        Get-ChildItem tmpHome: | Select-Object -first 3 | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p13
        Remove-PSDrive tmpHome -Scope Script
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        $p14
        Get-ChildItem $pshome -file | Select-Object -first 1 | Out-Host
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p15
        if ($PSEdition -eq 'Desktop') {
            $errMsg = @"
Get-ChildItem : A parameter cannot be found that matches parameter name 'file'.
At line:1 char:25
+ Get-ChildItem Function: -file
+ ~~~~~
    + CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
"@

        Write-Host $errMsg -ForegroundColor $host.PrivateData.ErrorForegroundColor
        }
        else {
            #On non-Windows systems this causes an error that stops
            #the pipeline and breaks the script, so I'll fake the expected error
           "$($PSStyle.Formatting.Error)Get-ChildItem: A parameter cannot be found that matches parameter name 'file'.$($PSStyle.Reset)"
        }
        "`n"
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        $p16
        $script:pg++ ; Pause $script:pg $pgCount
    },
    {
        Clear-Host
        #suppress Write-Progress
        $p17
        if ($IsCoreCLR) {
            Get-Help about_*_provider -ProgressAction SilentlyContinue| Out-Host
        }
        else {
            $ProgressPreference = "SilentlyContinue"
             Get-Help about_*_provider | Out-Host
        }
        $script:pg++ ; Pause $script:pg $pgCount
    }
)

#keep a navigation page counter
$pgCount = $pages.count
<#
There is an overlap in functionality between $i and $pg but they are
separate counters because there may be times I need to display a "page"
of information into two pages and want to maintain a page number.
#>

for ($script:i = 0; $script:i -lt $pages.count; $script:i++) {
    Invoke-Command -ScriptBlock $pages[$script:i]
}

#endregion

#this will come before the Profiles tutorial
if ($full) {
    &$tutorials['PowerShell Profiles'] -Full
}
elseif ($Menu) {
    Start-PSTutorial
}