Powershell–Chaud (Show:-)) les jonctions !…

Je me lance dans la rédaction d’un petit article sur la gestion des jonctions et autres liens NTFS sous Powershell. En fait, cette particularité me taraude l’esprit depuis un bon nombre d’années déjà et je m’accommodais des commandes historiques “cmd /c DIR /AL” et autre “cmd /c MKLINK”.

Introduction

Afin de planter le décor, il faut peut être rappeler que les liens et jonctions que je vais évoquer, existent pratiquement depuis que NTFS existe. Ils ont toutefois été exploités tardivement par les systèmes Microsoft lors de la sortie de NT6 (pour ne pas dire Vista). Ils ont essentiellement un rôle de comptabilité avec les systèmes antérieurs, afin de rediriger les “anciens dossiers” vers les “nouveaux”.

Typiquement, voici quelques exemples :

<JONCTION>     Documents and Settings –> [C:\Users]
<SYMLINKD>     All Users [C:\ProgramData]
<JONCTION>     Default User [C:\Users\Default]

Sous une invite de commande traditionnelle (cmd.exe), il suffit d’utiliser la commande “dir” avec le commutateur “/aL” (comme “link”) pour afficher et surtout distinguer les liens des dossiers “normaux”, notés “<REP>”.

dir-AL

Bon, ça c’est pour les afficher, et pour les gérer (création ou suppression), il faut “encore” recourir à la commande “MKLINK.exe”.

mklink

Bref, Powershell fait plutôt pâle figure dans ce domaine et on va donc essayer d’y remédier.

 

Introspection de Powershell

Donc, si on regarde l’applet de commande “dir” (ou “ls”, ou “gci” ou “Get-ChildItem” Sourire) sous Powershell, on constate que la collection renvoyée pour l’énumération , est un tableau de type “System.IO.FileInfo” et “System.IO.DirectoryInfo”

dir C:\ | Get-Member

 

Sous Powershell, l’affichage des résultats (collections d’objets) utilise un formatage par défaut. Cet affichage par défaut est en fait issu de la combinaison de 2 éléments de configuration  connus sous les noms de  : “Type definition” et “View definition”

En premier lieu, nous remarquerons que l’élément “Mode” d’un objet “DirectoryInfo” n’est pas une “Property” mais un “CodeProperty”. Ensuite, sans vouloir entrer dans des considérations techniques trop pointues, sachez qu’il est possible de modifier globalement  n’importe quel objet de type DotNET au travers du fichier de définition « $PSHOME\Types.ps1xml » dont voici un petit extrait :

Powershell-Types

Modification de Powershell

Afin de ne pas modifier ce fichier par défaut, nous allons créer notre propre fichier de définition de types, nommé par exemple “My.Types.ps1xml” dans notre dossier personnel de profil, soit “$profile | split-path

New-Item "$($PROFILE|Split-Path)\My.Types.ps1xml" -ItemType file –force

 

puis, nous allons ajouter le contenu suivant :

<Types>
   <Type>
        <Name>System.IO.DirectoryInfo</Name>
        <Members>
            <ScriptProperty>
                <Name>ExtMode</Name>
                <GetScriptBlock>
                    $catr = "";
                    if ( $this.Attributes -band 1024 ) { $catr += "j" } else { $catr += "-" };
                    if ( $this.Attributes -band 16 ) { $catr += "d" } else { $catr += "z" };
                    if ( $this.Attributes -band 32 ) { $catr += "a" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 1 )  { $catr += "r" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 2 )  { $catr += "h" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 4 )  { $catr += "s" } else { $catr += "-" } ;
                    $catr
                </GetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>
    <Type>
        <Name>System.IO.FileInfo</Name>
        <Members>
            <ScriptProperty>
                <Name>ExtMode</Name>
                <GetScriptBlock>
                    # Ajout d’un tiret "-" afin d’assurer l’alignement des informations
                    $catr = "-";
                    if ( $this.Attributes -band 16 ) { $catr += "d" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 32 ) { $catr += "a" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 1 )  { $catr += "r" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 2 )  { $catr += "h" } else { $catr += "-" } ;
                    if ( $this.Attributes -band 4 )  { $catr += "s" } else { $catr += "-" } ;
                    $catr
                </GetScriptBlock>
            </ScriptProperty>
          </Members>
     </Type>
</Types>

 

En fait, la détection d’un lien ou d’une jonction est basée sur l’application d’un « ET logique » (-band) sur l’attribut (reparsepoint) avec la valeur « 1024 » pour un filtrage « bit à bit ».

Notez également que la propriété ajoutée a été nommée “ExtMode” afin de ne pas entrer en conflit avec la propriété “Mode” existante.

Ensuite, une fois ce fichier enregistré, vous devrez ajouter le code suivant à votre profil personnel (notepad $PROFILE) afin qu’il soit exécuté lors de chaque session Powershell.

$CustomTypes = $profile | split-path | join-path -childPath "My.Types.ps1xml"
Update-TypeData $CustomTypes

 

La commande “Update-TypeData” permet de recharger en mémoire les définitions de types personnalisés contenues dans les fichiers *.types.ps1xml

 

Vérification fonctionnelle

Fermez puis rouvrez votre session Powershell afin de vérifier le fonctionnement de cette modification globale, puis entrez la commande suivante :

dir C:\ | Get-Member

 

Vous devriez voir apparaitre une nouvelle propriété “ScriptProperty” nommée  “ExtMode”. Toutefois, si vous entrez la commande “dir” habituelle, cette propriété ne sera pas affichée par défaut. Il faudra donc le préciser lors de l’appel :

dir c:\ –force | select ExtMode, LastAccessTime, Length, Name

 

Au besoin, il vous suffit de créer une fonction comme suit :

function ldir
{
param (
[string]$SourceDirectory = ".",
[string]$FileTypeFilter = "*",
[switch]$Recurse,
[switch]$Force
)
get-childitem $SourceDirectory -recurse:$Recurse -filter $FileTypeFilter -force:$Force | select ExtMode, LastWriteTime, Length, Name
}

 

Exemple d’usage :

ldir -Force -Recurse | where {$_.extmode -like 'j*'}

 

Voilà, pour l’énumération des jonctions en Powershell, soit l’équivalent de “cmd /c dir /s /al” Triste. Je reconnais que c’est “chaud” comme solution mais je n’ai pas trouvé mieux.

Cette 1ère partie est fortement inspirée de la solution de Scott Hanselman http://www.hanselman.com/blog/MakingJunctionsReparsePointsVisibleInPowerShell.aspx


Gestion des jonctions

Quant à la création et la suppression de ces fameuses jonctions, plusieurs solutions s’offrent à vous :

– Vous pouvez simplement vous contenter d’utiliser (comme moi) la commande originale “cmd /c mklink …” (avec les commutateurs d’origine, sans complétion)

– Ecrire quelques fonctions (selon qu’il s’agisse d’une jonction, d’un lien  physique ou d’un lien symbolique ) comme suit :

Function New-SymLink ($link, $target)
{
    if (test-path -pathtype container $target)
    {
        $command = "cmd /c mklink /d"
    }
    else
    {
        $command = "cmd /c mklink"
    }

    invoke-expression "$command $link $target"
}

Function Remove-SymLink ($link)
{
    if (test-path -pathtype container $link)
    {
        $command = "cmd /c rmdir"
    }
    else
    {
        $command = "cmd /c del"
    }

    invoke-expression "$command $link"
}

 

– Ecrire une fonction globale, ce qui revient à réécrire l’outil “mklink”. Dans ce cas, ne perdez pas votre temps et utilisez le module de Joshua Poehls   http://zduck.com/2013/mklink-powershell-module/

– Enfin, faites un tour du coté des « PowerShell Community Extensions » alias PSCX ici– Vous y trouverez tout plein de compléments bien pratiques pour compléter quelques lacunes de Powershell.

Voilà, je pense que l’essentiel est dit…

Bonne continuation

Christophe

Post-scriptum :

Tout vient à point à qui sait attendre … Powershell v5 (et donc Windows 10) intègre(ra) par défaut l’affichage des liens et jonctions dans la commande dir / get-childitem. La preuve en image :

$Host.Version
dir -Force

Powershell5-dir

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *