none
When is a backslash not a backslash RRS feed

  • Question

  • Hey, Scripting Guy!  A file path I retrieved with GetAssemblies could not be deconstructed by Split-Path.

    I retrieved the FullName property of the locally loaded assemblies with
    [AppDomain]::CurrentDomain.GetAssemblies().FullName
    and I noticed that, unlike PowerShell modules, my local modules are named with their full file system path.

    That is,
    powershell_ise, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
    but
    ⧹C։⧹Users⧹BereJA⧹Documents⧹WindowsPowerShell⧹Modules⧹Formatting⧹Formatting.psm1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

    So no big deal, I extract the first element of FullName with
    ( <assembly>.FullName -split ',' )[0]
    and then try to get that element's friendly last part with
    Split-Path ( <element> ) -Leaf

    But what I get back is original string, when I am expecting just Formatting.psm1.

    It turns out that <assembly>.FullName is not returning the ordinary Unicode backslash '\' = [char]0x005C that is expected by Split-Path.  It is instead returning Unicode "big reverse solidus" = [char]0x29F9.  And indeed, after I
    <element>.Replace( [char]0x29F9, '\' )
    Split-Path returns what I expected.

    I know, I know, you're going to tell me this is by design.  :-(

    John

    Sunday, November 6, 2016 1:29 PM

All replies

  • Not sure what you are asking about as that is not what we get with an assembly definition.

    <assembly>.Fullname does not return a path with a registered assembly.  It returns the name like this:

    mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

    What is it that you are calling "local modules"?

    Try this:

    [AppDomain]::CurrentDomain.GetAssemblies().Location

    [AppDomain]::CurrentDomain.GetAssemblies().Location|%{Split-Path $_ -leaf}


    \_(ツ)_/



    • Edited by jrv Sunday, November 6, 2016 1:51 PM
    Sunday, November 6, 2016 1:46 PM
  • I believe your issue it that the "FullName" of the module is a name generated by Windows to ensure a unique name string.  It is not intended as a pathname.  "Location" is the correct property to use as the pathname of the assembly.


    \_(ツ)_/


    • Edited by jrv Sunday, November 6, 2016 1:56 PM
    Sunday, November 6, 2016 1:55 PM
  • You can also do this:

    [AppDomain]::CurrentDomain.GetAssemblies().GetModules().Name


    \_(ツ)_/

    Sunday, November 6, 2016 1:59 PM
  • For .psm1 modules imported from my modules folder:

    1. They show up in [AppDomain]::CurrentDomain.GetAssemblies(), and
    2. [AppDomain]::CurrentDomain.GetAssemblies().Location returns $Null for these, and
    3. [AppDomain]::CurrentDomain.GetAssemblies().GetModules().Name returns '<In Memory Module>' for these.
    4. [AppDomain]::CurrentDomain.GetAssemblies().FullName is the only one that returns something that appears to contain a file path.

    So regardless of whether the entire FullName is intended to be unique, with embedded version and other info, I still ask "shouldn't the initial portion, which so clearly resembles a path, be able to be used as a path".

    John

    Sunday, November 6, 2016 2:32 PM
  • PM1 modules are not true assemblies but can generate or load assemblies.  Get-Module is the only reliable way to get the information.  The "FullName" is a generated name used to identify the module globally.  Since it is in memory and not a standard assembly that is what you will get.  By manipulation it you may be able to get the path.

    For all modules I have loaded "Location" gives the correct path.

    What module specifically shows this?

    Her is what MSCORLIB looks like as a loaded assembly:

    Location            :
    CodeBase            :
    ImageRuntimeVersion : v4.0.30319
    FullName            : Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
    EntryPoint          :
    DefinedTypes        : {}
    Evidence            : {<System.Security.Policy.Url version="1">
                          <Url>file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_64/mscorlib/v4.0_4.0.0.0__b77a5c561934e089/msc
                          orlib.dll</Url>
                          </System.Security.Policy.Url>
                          , <System.Security.Policy.Zone version="1">
                          <Zone>MyComputer</Zone>
                          </System.Security.Policy.Zone>
                          }
    PermissionSet       : {}
    SecurityRuleSet     : Level1
    ManifestModule      : RefEmit_InMemoryManifestModule
    ReflectionOnly      : False
    GlobalAssemblyCache : False
    HostContext         : 0
    IsDynamic           : True
    EscapedCodeBase     :
    ExportedTypes       :
    IsFullyTrusted      : True
    CustomAttributes    : {[System.Security.SecurityTransparentAttribute()],
                          [System.Security.SecurityRulesAttribute((System.Security.SecurityRuleSet)1)]}
    Modules             : {<In Memory Module>}
    

    Notice it is generated "in-memory" and runs at security level 1 which is as good as no permissions.  It is the foundation for PowerShell.  Ther eis no Location and the FullName is generated.  There are likely many methods for generating names that do not conflict.


    \_(ツ)_/

    Sunday, November 6, 2016 2:49 PM
  • Here is a way that should give all paths correctly:

    [AppDomain]::CurrentDomain.GetAssemblies().Evidence|%{[xml]$_}|%{$_.SelectSingleNode('//Url')}


    \_(ツ)_/

    Sunday, November 6, 2016 2:53 PM
  • Here is how to extract the name from the system generate "Evidence" which shows the actual source of the item.  It will likely return nothing fo modules that are entirely generated in code.

    [AppDomain]::CurrentDomain.GetAssemblies().Evidence |
    	%{ [xml]$_ } | 
    	%{ $_.SelectSingleNode('//Url') } | 
    	select -expand '#text' | 
    	%{ Split-Path $_ -leaf }
    


    \_(ツ)_/

    Sunday, November 6, 2016 3:09 PM
  • For the purpose of this thread I have constructed a Demo.psm1 file to illustrate the things I have been writing about.

    The actual contents of the file are not interesting and have nothing to do with this thread.
    They happen to create a variable named $Boxes containing name-addressable box-drawing characters.  Ignore that.

    Create a folder named Demo on <user>'s Modules folder
        (C:\Users\<user>\Documents\WindowsPowerShell\Modules).
    Copy Demo.ps1 onto the Demo folder.

    1. Once imported, the module shows up in [AppDomain]::CurrentDomain.GetAssemblies()
    2. Its FullPath contains the non-standard backslash
    3. No suggestions so far have resulted in a standard path

    Steps
    -----

        Import-Module Demo

    Get all assemblies with
        $a = [AppDomain]::CurrentDomain.GetAssemblies()

    Select the Demo object with
        $b = $a | where { $FullName -match demo }

    You are welcome to try to find a property of $b that resembles the false path embedded in
        $b.FullName
            or as I tried
        $c = ($b.FullName -split ',')[0]

    As I have been explaining
        Split-Path $c -Leaf
    returns the full unmodified $c, while
        Split-Path ( $c.Replace( [char]0x29F9, '\' ) ) -Leaf
    returns demo.psm1

    A satisfactory resolution to this thread would be a sequence that returns a proper path.  None of the suggestions so far accomplishes that.  I wouldn't even be asking about this except for the fact that Demo shows up in the assemblies list.

    John

    File: Demo.ps1:

    #----------------------------------------------------------------------#
    <#  +------------------------------+
        ¦ Construct the $Box Variable  ¦
        +------------------------------+    #>
    if (-not (Test-Path Variable:Boxes) ) {
        class BoxLine   { [char]$Horiz; [char]$Vert }
        class BoxCorner { [char]$UL; [char]$UR; [char]$LR; [char]$LL }
        class BoxJoint  { [char]$T; [char]$R; [char]$B; [char]$L; [char]$Ctr }

        class BoxElement { [BoxLine]$Line; [BoxCorner]$Corner; [BoxJoint]$Joint; [string[]]$Example }

        class Boxes { [BoxElement]$Light; [BoxElement]$Heavy; [BoxElement]$Arced; [BoxElement]$Double }
    & {
        <# Light #>
        $Line   = [BoxLine]    @{ Horiz=0x2500; Vert=0x2502                                   }
        $Corner = [BoxCorner]  @{ UL   =0x250C; UR  =0x2510; LR=0x2518; LL=0x2514             }
        $Joint  = [BoxJoint]   @{ T    =0x252C; R   =0x2524; B =0x2534; L =0x251C; Ctr=0x253C }

        [string[]]$Example += "$($Corner.UL)$("$($Line.Horiz)"*2)$($Joint.T)$("$($Line.Horiz)"*2)$($Corner.UR)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Joint.L)$("$($Line.Horiz)"*2)$($Joint.Ctr)$("$($Line.Horiz)"*2)$($Joint.R)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Corner.LL)$("$($Line.Horiz)"*2)$($Joint.B)$("$($Line.Horiz)"*2)$($Corner.LR)"

        $Light  = [BoxElement] @{ Line=$Line; Corner=$Corner; Joint=$Joint; Example=$Example }
        Remove-Variable Example

        <# Heavy #>
        $Line   = [BoxLine]    @{ Horiz=0x2501; Vert=0x2503                                   }
        $Corner = [BoxCorner]  @{ UL   =0x250F; UR  =0x2513; LR=0x251B; LL=0x2517             }
        $Joint  = [BoxJoint]   @{ T    =0x2533; R   =0x252B; B =0x253B; L =0x2523; Ctr=0x254B }

        [string[]]$Example += "$($Corner.UL)$("$($Line.Horiz)"*2)$($Joint.T)$("$($Line.Horiz)"*2)$($Corner.UR)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Joint.L)$("$($Line.Horiz)"*2)$($Joint.Ctr)$("$($Line.Horiz)"*2)$($Joint.R)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Corner.LL)$("$($Line.Horiz)"*2)$($Joint.B)$("$($Line.Horiz)"*2)$($Corner.LR)"

        $Heavy  = [BoxElement] @{ Line=$Line; Corner=$Corner; Joint=$Joint; Example=$Example }
        Remove-Variable Example

        <# Arced #>
        $Line   = [BoxLine]    @{ Horiz=0x2500; Vert=0x2502                                   }
        $Corner = [BoxCorner]  @{ UL   =0x256D; UR  =0x256E; LR=0x256F; LL=0x2570             }
        $Joint  = [BoxJoint]   @{ T    =0x252C; R   =0x2524; B =0x2534; L =0x251C; Ctr=0x253C }

        [string[]]$Example += "$($Corner.UL)$("$($Line.Horiz)"*2)$($Joint.T)$("$($Line.Horiz)"*2)$($Corner.UR)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Joint.L)$("$($Line.Horiz)"*2)$($Joint.Ctr)$("$($Line.Horiz)"*2)$($Joint.R)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Corner.LL)$("$($Line.Horiz)"*2)$($Joint.B)$("$($Line.Horiz)"*2)$($Corner.LR)"

        $Arced  = [BoxElement] @{ Line=$Line; Corner=$Corner; Joint=$Joint; Example=$Example }
        Remove-Variable Example

        <# Double #>
        $Line   = [BoxLine]    @{ Horiz=0x2550; Vert=0x2551                                   }
        $Corner = [BoxCorner]  @{ UL   =0x2554; UR  =0x2557; LR=0x255D; LL=0x255A             }
        $Joint  = [BoxJoint]   @{ T    =0x2566; R   =0x2563; B =0x2569; L =0x2560; Ctr=0x256C }

        [string[]]$Example += "$($Corner.UL)$("$($Line.Horiz)"*2)$($Joint.T)$("$($Line.Horiz)"*2)$($Corner.UR)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Joint.L)$("$($Line.Horiz)"*2)$($Joint.Ctr)$("$($Line.Horiz)"*2)$($Joint.R)"
                  $Example += "$($Line.Vert)$(" "*2)$($Line.Vert)$(" "*2)$($Line.Vert)"
                  $Example += "$($Corner.LL)$("$($Line.Horiz)"*2)$($Joint.B)$("$($Line.Horiz)"*2)$($Corner.LR)"

        $Double = [BoxElement] @{ Line=$Line; Corner=$Corner; Joint=$Joint; Example=$Example }
        Remove-Variable Example

        Set-Variable Boxes -Value ( [Boxes] @{ Light=$Light; Heavy=$Heavy; Arced=$Arced; Double=$Double } ) -Scope Global
      }
    }
    #----------------------------------------------------------------------#


    Sunday, November 6, 2016 4:22 PM
  • My best guess is a bug in processing when a class is declared. If I create a module with a manifest and one function the fullname is correct and there are no odd characters. If I add your classes to the module ALL of the path strings look like your version.

    I suggest posting this in "uservoice" as a bug. https://windowsserver.uservoice.com/forums/301869-powershell

    Post back with the link and I will add my support as having tested the same issue.


    \_(ツ)_/


    • Edited by jrv Sunday, November 6, 2016 5:49 PM
    Sunday, November 6, 2016 5:48 PM
    • Edited by JABerenberg Sunday, November 6, 2016 7:03 PM Fix link
    Sunday, November 6, 2016 7:01 PM
  • Module loading has been changed in WMF 5.  Here is the correct way to load a module with classes:

    D:\scripts> using module demo
    D:\scripts> get-module demo|select RootModule, ModuleBase
    
    RootModule ModuleBase
    ---------- ----------
    Demo.psm1  D:\Users\jvierra\Documents\WindowsPowerShell\Modules\Demo

    This is undocumented.  See this: http://info.sapien.com/index.php/scripting/scripting-classes/import-powershell-classes-from-modules


    \_(ツ)_/

    Monday, November 7, 2016 5:25 PM
  • JRV, I fear that in your enthusiasm you may have lost sight of the primary topic of this thread I started.

    In your last post you correctly observe how, for an object returned by function Get-Module, to retrieve the file path.  Well yes, of course, but off-topic.

    My thread question was instead how to get similar information from the collection of objects retrieved by method GetAssemblies().  Following your post I did switch from 'Import-Module' over to 'Using Module', but FullName remains the only property of the assembly objects that contains anythng resembling a path, and it is still populated with "imposter" backslashes that can't be parsed by Split-Path.  Let's not be in a rush to withdraw my bug report over at windowsserver.uservoice.

    And now if I, too, may go off-topic, I have an observation about 'Using Module'.  There is a warning posted by both 'Import-Module' and 'Using Module' whenever a module contains a function whose name begins with an unapproved verb, for example 'Monitor-'.  Import-Module has a switch, -DisableNameChecking, to suppress the warning.  If 'Using Module' has a similar switch I couldn't find it.  Oh, and the warning text is identical for both, right down to suggesting, even with 'Using Module' that you 'run the Import-Module command again' with the Verbose parameter.  But 'Using Module' doesn't permit -Verbose.

    John
    Monday, November 7, 2016 9:22 PM
  • If you read the blog then you understand that GetAssemblies() will not return a properly loaded class module.  "class" modules are not intended to be loaded with Import-Module.  They need to be loaded with "using module" to load correctly then Get-Module will return correct information.

    If you load a module with "classes" it will not load properly. The documentation for this has not yet been released by MS but the blogger, June Blender has access to MS internal resources and has gleaned out the information.

    The options for "using" have not been published yet but similar switches will likely become available.

    Here is a bit more about the "using" keyword: https://www.automatedops.com/blog/2016/01/28/testing-powershell-classes/


    \_(ツ)_/

    Monday, November 7, 2016 9:30 PM