Is there a way to override `~` (tilde) location in powershell - 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 ~

Related

How to refer to HKEY_CLASSES_ROOT in PowerShell?

New-Item -Path "HKCR:\Directory\Background\shell\customname" -Force
I've been doing the same thing for HKCU and KHLM but when I try HKCR I get errors in PowerShell. how am I supposed to do it for HKEY_CLASSES_ROOT?
I searched for a solution but couldn't find any.
Okay I figured out on my own,
checked Get-PSDrive
and saw the only registry aliases available by default on Windows/PowerShell are
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
so, what I did, following this, was to add a new alias for HKEY_CLASSES_ROOT that is called HKCR
New-PSDrive -Name "HKCR" -PSProvider Registry -Root "HKEY_CLASSES_ROOT"
Defining a custom drive whose root is HKEY_CLASSES_ROOT, as shown in your own answer, is definitely an option, especially for repeated use.
Ad hoc, you can alternatively use the Registry:: provider prefix directly with native registry paths:
New-Item -Path 'Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\customname' -Force
Note:
The Registry part of the prefix is the provider name, as shown in Get-PSProvider's output.
Hypothetically, multiple providers with the same name could be registered, in which case you can prefix the name with the implementing module name for disambiguation; in the case of the registry provider, this module-qualified prefix is Microsoft.PowerShell.Core\Registry::[1] However, it's fair to assume that no third-party providers will choose a name that conflicts with the providers that ship with PowerShell, so Registry:: (or registry::, case doesn't matter), should do.
Note that the module-qualified provider name does show up in the prefix of the .PSPath property that provider items, such as reported by Get-Item and Get-ChildItem, are decorated with, e.g.:
PS> (Get-Item HKCU:\Console).PSPath
Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Console
[1] Note that the Core part of the name does not refer to the PowerShell (Core) edition; it simply denotes a module that is at the core of either edition.

How to get the file directory for a custom cmdlet

The way our business is set up, our custom cmdlets are spread out across the network in several different larger files. We refer to these files in the usual "Microsoft.PowerShell_profile.ps1"
Is there something I can run within Powershell to find where a cmdlet is running from instead of manually going through all the files referenced in "Microsoft.PowerShell_profile.ps1" to find it?
E.g. if I have written a cmdlet called Get-UserExpiry and it is saved in C:\User\Name\Documents\CustomCmds.ps1, and I include that file path in Microsoft.PowerShell_profile.ps1, is there a command I can use to find that file path if all I know is the cmdlet name?
Get-Command is what you need. That being said, depending on the command you are testing and the type of the command (external application, function, cmdlet, profile function), the command path won't always be assigned to the same property / subproperty.
Here's a way to get the path no matter where it is disclosed.
Function definition
Here we check the possible locations of the path based on the Get-Command result, filter out everything that is $null or empty and pick the first result we get.
Function Get-CommandLocation($Command) {
$definition = (Get-Command -Name $Command)
#(
$definition.Module.Path
$definition.Source
$definition.ScriptBlock.File
) | Where-Object { $_ } | Select-Object -First 1
}
Examples
Get-CommandLocation Get-Item # Native cmdlet
# Value obtained from (Get-Command -Name 'Get-Item').Module.Path
# Return C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1
Get-CommandLocation Edit-Profile # Custom profile function (Test with a function in your profile (if any))
# Value obtained from(Get-Command -Name 'Edit-Profile').ScriptBlock.File
# Return C:\Users\SagePourpre\Documents\WindowsPowerShell\Microsoft.VSCode_profile.ps1
Get-CommandLocation New-ExternalHelp # PlatyPS module downloaded from the gallery
# Value obtained from (Get-Command New-ExternalHelp).Module.Path
# Return C:\Program Files\WindowsPowerShell\Modules\platyPS\0.14.2\platyPS.psm1
Get-CommandLocation -command cmd # External Program
# Value located in (Get-Command -Name 'cmd').Source
# Return C:\Windows\system32\cmd.exe

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.

How to retrieve original, unresolved (unexpanded) registry value?

In the System Properties > Environment Variables > User variables > PATH contains:
%USERPROFILE%\Downloads\SysinternalsSuite;%USERPROFILE%\bin
The value can be retrieved with:
PS C:\src\t> (Get-ItemProperty -Path HKCU:\Environment).PATH
C:\Users\lit\Downloads\SysinternalsSuite;C:\Users\lit\bin
Is there any way to get the original value without variable expansion? It almost seems like Get-ItemProperty needs a -Raw switch.
PetSerAl, as many times before, has provided an effective solution in a terse comment on the question.
Indeed, PowerShell's Get-ItemProperty / Get-ItemPropertyValue cmdlets currently (PowerShell 7.3.0) lack the ability to retrieve a REG_EXPAND_SZ registry value's raw value, meaning the value as stored in the registry before the embedded environment-variable references (e.g., %USERPROFILE%) are expanded (interpolated).
Direct use of the .NET API is therefore needed:
(Get-Item -Path HKCU:\Environment).GetValue(
'PATH', # the registry-value name
$null, # the default value to return if no such value exists.
'DoNotExpandEnvironmentNames' # the option that suppresses expansion
)
See [Microsoft.Win32.RegistryKey].GetValue().
Note: 'DoNotExpandEnvironmentNames' is automatically converted to [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentVariables by PowerShell; you may use the latter as well.

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

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.