Powershell - Why does $ENV | GET-MEMBER not work? - powershell

I come from a C, C++, C#, python background so i'm applying this thought pattern to Powershell which i'm learning from scratch but I'm a little confused so far as at first glance it seems to be inconsistent and does not follow a fixed base class type structure for all objects so that things can be queried in a consistent manner.
The following works fine:
$host | get-member
$env:username
So $env is a valid object but this does not work:
$env | get-member
These also do not work:
$env.gettype()
dir $env
dir $env:
but this type query on $host does so I'm assuming $host is a .net variable but $env is not?
$host.gettype()
I found that env: also works with dir (aka get-childitem) but this colon is yet another type of notation i'm unfamiliar with and things are starting to get very confusing now. This does not seem to be a string format in this case which I have seen some google posts about so what is it? It behaves like a member selection or dictionary key specifier. If it is a member selector or dictionary key then i would expect get-member to work because it is a standard object.
This outputs the variables and values that I wanted but I don't understand why this syntax is used. This is not DOS syntax either so what's going on here?
dir env:
But dir $env seems to equate to dir $env:userprofile???? why?
Therefore $host appears to be a .net object but $env or env: is something else completely different and I've no idea what object type it is in the grand scheme of things and cannot seem to query it's type with by conventional means. Initial thoughts are that it is a list object of sorts because get-childitem works with it but other than that I'm completely lost.
I'm clearly missing something here so can someone steer me in the right direction please?

Get-Help 'about_Providers' -ShowWindow shows that env: is drive in Environment Provider, i.e. one of Windows PowerShell providers.
BUILT-IN PROVIDERS: Windows PowerShell includes a set of built-in
providers that you can use to access the different types of data
stores.
Provider Drive Data store
-------- ----- ----------
Alias Alias: Windows PowerShell aliases
Certificate Cert: x509 certificates for digital signatures
Environment Env: Windows environment variables
FileSystem * File system drives, directories, and files
Function Function: Windows PowerShell functions
Registry HKLM:, HKCU: Windows registry
Variable Variable: Windows PowerShell variables
WSMan WSMan: WS-Management configuration information
* The FileSystem drives vary on each system.
You can also create your own Windows PowerShell providers, and you can
install providers that others develop. To list the providers that are
available in your session, type: get-psprovider.
That's why Get-ChildItem env: works in contrary to dir $env:, dir $env etc.

First thing to note is that $env and $env:username are not related. $env is just a variable and normally it does not exists, because nobody assign anything to it. Using colon in variable name (like $env:username, with exception to some predefined prefixes: global:, script:, local:, private: and variable:) is a special syntax, which allows to access to PowerShell provider item content with variable syntax. It works with any PowerShell provider which implement content cmdlets: ${C:\Windows\System.ini} or $function:prompt. That syntax is equivalent of calling of Get-Content or Set-Content for given PowerShell path.

My 2 cents:
Try get-psdrive and you will get something like:
Name Used (GB) Free (GB) Provider Root
Env Environment
So it seems to be something like a driver in batch.

Related

How two concatenate environment variables names in powershell

Say I have the following environment variables:
a = Poke
b = mon
Pokemon= Feraligatr
I want to be able to concatenate a and b environment variables to get the variable name Pokemon and the get Pokemon value like $($env:ab) or $($env:$($env:a)$($env:b)) (This examples does not work)
Building on the helpful comments:
You're looking for indirection, i.e. the ability to refer to an environment variable indirectly, via another (environment) variable(s) storing the target variable's name.
PowerShell-idiomatic solution:
Use the Env: drive in combination with the Get-Content cmdlet:
# The target environment variable.
$env:Pokemon='bingo!'
# The variables that, in combination, return the *name*
# of the target environment variable.
$env:a = 'Poke'
$env:b = 'mon'
# Use Get-Content and the env: drive to retrieve
# an environment variable by an *indirectly* specified name.
# Note:
# * env:$env:a$env:b is treated like "env:$env:a$env:b",
# i.e. an expandable (interpolating string).
# * For better visual delineation of the variables, use:
# env:${env:a}${env:b}
# * `-ErrorAction Ignore` ignores the case when the target var.
# doesn't exist (quietly returns $null`)
# -> 'bingo!'
Get-Content -ErrorAction Ignore env:$env:a$env:b
# Alternative, with explicit string concatenation.
Get-Content -ErrorAction Ignore ('env:' + $env:a + $env:b)
Note:
To set environment variables indirectly, use the Set-Content cmdlet; e.g.:
$varName = 'FOO'
Set-Content env:$varName BAR # $env:FOO now contains 'BAR'
Applying the same technique to regular shell variables (non-environment variables), requires either use of the variable: drive, or, for more flexibility, the Get-Variable and Set-Variable cmdlets - see this answer.
More more information about expandable (interpolating) string literals such as "env:$env:a$env:b", see the conceptual about_Quoting help topic.
.NET API alternative:
As Max points out, you can also use the static System.Environment.GetEnvironmentVariable .NET method:
[Environment]::GetEnvironmentVariable("${env:a}${env:b}")
For more information about calling .NET API methods from PowerShell, see the conceptual about_Methods help topic.

Is there a way to override `~` (tilde) location in powershell

The problem
I am trying to change the expanded path of the ~ character in PowerShell without changing the $Env:USERPROFILE variable.
What I've tried
My initial approach was to create an alias to a function that references a differen environment variable, but it doesn't seem to work.
My code:
function Get-HomeDirectory {
# This is a custom function, it works as I have tested it
# It should be self-explanatory
Get-EnvironmentVariable -Key $HOME_DIR_KEY -User
}
Set-Alias -Name ~ -Value Get-HomeDirectory
Result
If I use Get-Help it works as expected:
PS> Get-Help ~
NAME
Get-HomeDirectory
SYNOPSIS
Returns the home directory of the current user.
SYNTAX
Get-HomeDirectory [<CommonParameters>]
DESCRIPTION
Retrieves the value set for the `$Env:USER_HOME_DIR` environment variable.
RELATED LINKS
Set-HomeDirectory
REMARKS
To see the examples, type: "Get-Help Get-HomeDirectory -Examples"
For more information, type: "Get-Help Get-HomeDirectory -Detailed"
For technical information, type: "Get-Help Get-HomeDirectory -Full"
For online help, type: "Get-Help Get-HomeDirectory -Online"
But if I try to use it I get:
PS> cd ~
PS> pwd
C:\Users\myuser
What works properly
Nevertheless, I can make it work if I pass it with a pipe (as it should), but that's not a very convenient way of using it:
PS> ~ | cd
PS> pwd
Path
----
B:\
Using a function (via an alias) to redefine ~ in arguments cannot work (except if the function call is enclosed in (...)), for the reasons explained in Jeroen Mostert's comment on your question.
There is a solution, though note that it redefines the meaning of an initial ~ - a placeholder for a provider's home location that is interpreted as such only by provider cmdlets - in file-system provider paths session-globally.
# Make the file-system provider use the value of
# env. var. USER_HOME_DIR as its home location.
(Get-PSProvider FileSystem).Home = $Env:USER_HOME_DIR
Note:
The change takes effect for the current session only; to make it persistent, you'd have to add it to your $PROFILE file - but note that loading of profiles can be bypassed via the CLI's -NoProfile parameter.
Every provider has its own - potentially undefined - home location. Thus, in the - atypical - event that the provider underlying the current location is not the file-system provider, ~ refers to that provider's home location; a contrived example:
# !! Fails, because the function provider has no home location defined.
Set-Location Function:; Get-Item ~

Is it possible to create namespaced environment variables in powershell? (like $custom:JSenv)

I was wondering if I can create a PSDrive like Env: for my project specific environment variables, like $custom:JSenv, $custom:root etc. instead of populating the Env: PSDrive.
I tried using New-PSDrive -Name custom -PSProvider Environment. I expexceted to see the custom: drive. However, in the actual output nothing changed.
Is creating aEnvironment PSDrive the right way to achieve the purpose?
The New-PSDrive cmdlet requires a Root parameter. I was unable to replicate your problem:
PS /> $null = New-PSDrive -Name custom -PSProvider Environment -Root ''
PS /> Get-Item -Path custom:
# => environment variables
All this really does, however, is give you a different method of calling the Env: drive since they use the same provider and the provider does not have a concept of scope.
No: To get what you want, you'd have to implement your own PowerShell [drive] provider that also implements the IContentCmdletProvider interface, because $<drive>:<path> (or ${<drive>:<path>}) is syntactic sugar - called namespace notation - for the following command:
Get-Content -Path <drive>:<path>
See the bottom section for more.
As for what you tried:
While it is possible to define a custom drive based on an existing provider, that drive will invariably reflect that provider's items, without any ability to define your own.
In other words:
New-PSDrive -Name custom -PSProvider Environment -Root ''
will simply make drive custom: an alias of the env: drive.
While you could define your custom entries as environment variables, they would show in addition to the preexisting ones.
Implementing a custom PowerShell drive [provider]:
Writing your own [drive] provider requires compiled code, so you cannot implement custom drives in PowerShell itself.
As of Windows PowerShell v5.1 / PowerShell Core 6.1.0, implementing providers is nontrivial, but simplifying that is being considered for a future PowerShell Core version.
However, there are third-party helper modules that greatly simplify the process and do allow you to implement custom drives in PowerShell code.
Note: I haven't verified that your specific use case can be implemented.
Simplex: PS Gallery link - source code and documentation - easy-to-use DSL, but not all features are exposed.
SHiPS: PS Gallery link - source code and documentation - more complex, but more fully-featured.

What is the difference between Env: and [System.Environment]?

Given #mklement's good answer in how to sort a txt file in specific order in Powershell, it made me wonder...
What is the difference between Env: and [System.Environment]?
Why is [Environment]::NewLine available, but $Env:NewLine does not exist?
[System.Environment] is the .Net framework's static environment class. It offers static methods related to the "environment" including ways to get environment variables.
$env:WHATEVER is special variable syntax whereby you can access the contents of a PSProvider using variable semantics.
What is a PSProvider? (also see about_Providers)
It's basically a way to access hierarchical data stores through a singular interface that is similar to a filesystem.
In fact FileSystem is itself a PSProvider in PowerShell, and this is why cmdlets that deal with files don't mention files (i.e.: Get-ChildItem, Get-Content, Set-Location, etc.).
To see available providers, use Get-PSProvider:
Name Capabilities Drives
---- ------------ ------
Registry ShouldProcess, Transactions {HKLM, HKCU}
Alias ShouldProcess {Alias}
Environment ShouldProcess {Env}
FileSystem Filter, ShouldProcess, Credentials {A, C, D, P...}
Function ShouldProcess {Function}
Variable ShouldProcess {Variable}
Certificate ShouldProcess {Cert}
WSMan Credentials {WSMan}
Use Get-PSDrive to just see the drives themselves:
Name Used (GB) Free (GB) Provider Root
---- --------- --------- -------- ----
A 103.23 46.58 FileSystem A:\
Alias Alias
C 200.02 22.77 FileSystem C:\
Cert Certificate \
D 1048.88 2677.13 FileSystem D:\
Env Environment
Function Function
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
O 49.34 10.16 FileSystem O:\
P 335.32 176.50 FileSystem P:\
S FileSystem S:\
Variable Variable
WSMan WSMan
Environment is also a PSProvider, which you can see by trying to navigate to its PSDrive:
Set-Location Env:
Or browsing it:
Get-ChildItem Env:
Or even getting its contents:
Get-Content Env:\COMPUTERNAME
Get-ChildItem Env: | Get-Content
The special variable syntax is a shorthand way of accessing certain PSProviders (they don't all support it), and it's most often used with Environment (I'd venture a guess that syntax was created specifically for the Environment).
That syntax does actually work for the file system but it's pretty awkward
${C:\users\briantist\test.txt}
Tab completion doesn't work correctly with that syntax.
If you try it for something like the registry provider, it will tab complete but throw an exception about it not being implemented when you run it.
Here's a fun useless one: use the Variable:\ provider:
$test = 'test'
$Variable:test

Differentiate Registry Key from Value paths

Is there an equivalent of [System.IO.DirectoryInfo] and [System.IO.FileInfo] for differentiating Registry Keys from Values? I want to evaluate a path and log for the user what the final target of the path is.
So far, this is what I have, and it's kinda ugly.
$path = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\SomeJunkHereToTestFailure'
try {
if ((Get-Item -path:$path -errorAction:stop).GetType().name -eq 'RegistryKey') {
'Registry Key'
}
} catch {
try {
if ((Get-ItemProperty -path:(Split-Path $path -parent) -name:(Split-Path $path -leaf) -errorAction:stop).GetType().name -eq 'PSCustomObject') {
'Registry Value'
}
} catch {
'What is this?'
}
}
Hoping for something more elegant and also consistently correct.
Ok, so after digging around for awhile, canonically speaking, this:
HKEY_LOCAL_MACHINE\SOFTWARE\Path\To\Something
Is always a path to a key.
Look at reg.exe query. Look at how Get-ItemProperty works. Notice in regedit.exe that you can copy key names, but not a "path" to a specific value. Look at how .reg files are written. Look at Registry.GetValue() and Registry.SetValue(). Or Registry.LocalMachine.OpenSubKey().GetValue(). Microsoft clearly thinks that registry paths always point to keys.
If you have software that's using HKEY_LOCAL_MACHINE\SOFTWARE\Path\To\A\Value to refer to a value in the registry, then they're not using standard registry paths that Windows understands.
That's why you run these the way you do:
reg.exe query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v "ProductName"
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name "ProductName"
[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion').GetValue('ProductName')
[Microsoft.Win32.Registry]::GetValue('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion','ProductName', $false)
That last one is nice because it doesn't error. If it can't find the value, it returns the third argument. It does, however, require the long hive name at the start as far as I can tell.
So, Test-Path -Container can tell you if it's a key. Microsoft.Win32.Registry.GetValue() can tell you if it's a value... with some manipulation.
Original answer (THIS DOES NOT WORK):
Use Test-Path:
$path = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\SomeJunkHereToTestFailure'
# Is it a Key?
Test-Path -Path $path -PathType Container
# Is it a Value?
Test-Path -Path $path -PathType Leaf
As for...
Is there an equivalent of [System.IO.DirectoryInfo] and [System.IO.FileInfo]
Not that I've ever seen or heard of to date.
However, take a look at the below to see if they can fit your need or you may be ableto tweak them to accomplish your goals.
Powershell Registry Cmdlets utilizing the .Net StdRegProv
This Script is a collection of PowerShell cmdlets that can be run in either x86 or x64 processes to handle common registry operations utilizing alternate registry views. This script is compatible with PowerShell versions 2.0 or later.
Download RegistryCmdlets.ps1
https://gallery.technet.microsoft.com/Powershell-Registry-19689888
Or of course is back to writing your own as you are posting here:
See also as you look at your effor to cultivate your own:
Registry Cmdlets: First Steps with CDXML
In this post, I’ll show you how to get started with CDXML. Before that, though, I’d better explain CDXML...
Cmdlet definition XML (CDXML) is a way to create a Windows PowerShell module from a WMI class by using the cmdlets-over-objects technology that was introduced in Windows PowerShell 3.0. As usual, it’s always easier to show you an example:
https://blogs.technet.microsoft.com/heyscriptingguy/2015/02/03/registry-cmdlets-first-steps-with-cdxml/
Registry Cmdlets: Working with the Registry
The bad news is that there aren’t any cmdlets for working with the registry. There’s a Registry provider, which means you can use the Item and ItemProperty cmdlets to manage the local registry—but there aren’t any specific registry cmdlets associated with the provider.
The good news is that we can adopt the approach that many teams at Microsoft have taken and create our own by using cmdlet definition XML (CDXML). A Common Information Model (CIM) class is wrapped in some fairly simple XML and published as a Windows PowerShell module. If you look in the Modules folder on a computer running Windows 8.1, Windows 8, Windows Server 2012 R2, or Windows Server 2012, you will find many files with a CDXML extension:
https://blogs.technet.microsoft.com/heyscriptingguy/2015/02/02/registry-cmdlets-working-with-the-registry
There's a problem with the premise of your question: Ending a registry path - composed of key names - with a value name is not supported - at least neither with the .NET Framework registry types nor with reg.exe nor with PowerShell's registry drive provider.
For instance, to refer to value WindowSize of registry key
HKEY_CURRENT_USER\Control Panel\Colors, you CANNOT use the following path:
# NOT a valid path, because 'WindowSize' is a *value*, not a *key*.
HKEY_CURRENT_USER\Control Panel\Colors\WindowSize
Even if you yourself decide to support such paths in your functions (and there may be utilities that similarly accept that), note that they are ambiguous, because a given registry key can have both a value and a subkey with a given name.
With that limitation in mind, your own code from your question is probably the best you can do (though it can be streamlined a little).
Is there an equivalent of [System.IO.DirectoryInfo] and [System.IO.FileInfo] for differentiating Registry Keys from Values?
The equivalent of [System.IO.DirectoryInfo] (filesystem directory) is [Microsoft.Win32.RegistryKey] (registry key).
By contrast, [System.IO.FileInfo] (filesystem file) has NO counterpart for registry values:
Registry values are represented as properties of registry items (keys) in the PS registry drive provider rather than items in their own right, and such properties are (awkwardly) represented as non-specific [System.Management.Automation.PSCustomObject] instances.
Similarly, registry values have no type representation in the .NET Framework (you only request their data by name).
Fundamentally, in terms of object models, registry values do not correspond to filesystem files; read on for more.
Optional reading: how the registry object model maps onto PowerShell drive-provider concepts in contrast with the filesystem object model
It comes down to this: in the filesystem, a directory's files are child items, whereas in the registry, a key's values are properties of that key.
Only container-type children are represented as child items in both providers (sub-directories in the case of the filesystem provider, sub-keys in case of the registry provider).
In the context of the FileSystem provider, files and directories are both (sub-types of) items.
Files are represented as [System.IO.FileInfo] instances, and directories as [System.IO.DirectoryInfo].
A leaf item is invariably of subtype file, whereas a container (interior) item is invariably of subtype directory (folder).
Therefore, you can use Test-Path -PathType Leaf to test for a file and Test-Path -PathType Container to test for a directory.
In the context of the Registry provider, it is only keys that are items.
Keys are represented as [Microsoft.Win32.RegistryKey] instances.
Since all keys are (potentially) containers, the registry provider has no concept of a leaf item.
Therefore, use of Test-Path -PathType <type> with a registry path is pointless,
because with -PathType Leaf the result is always $False, and with -PathType Container it is always $True.
By contrast, registry values are represented as properties (the equivalent of what the last-modified date is to a filesystem item, for instance).
Use the *-ItemProperty* cmdlets to read and write registry values.