How to get Powershell to recognise pscx modules? - powershell

I installed powershell community extensions because I want to use Show-Tree for this code.
# Log project state
# Assumes CWD is the project file
$Tree = Show-Tree -ShowProperty -ShowLeaf | Where-Object {$_ -inotmatch '^.*Property.*' -or $_ -match '^.*LastWriteTime\s.*'}
$i = 0
$culture = [Globalization.CultureInfo]::InvariantCulture
foreach($entry in $Tree){
if($entry -notmatch '^.*Property.*'){
$d = "{0:hh}:{0:mm}:{0:ss}" -f ([DateTime]::Parse($($Tree[$i+1] -replace '^.+=\s(.+)','$1'), $culture))
$Tree[$i] = "$d`t$entry"
}
$i++
}
$Tree = $Tree | Where-Object {$_ -inotmatch '^.*Property.*'}
foreach($s in $Tree){Write-Output $s}
This works OK in a PS debug session in VS Code but when I try to use it in MSBuild or with the powershell command prompt, it complains that it can't find the extension module...
Show-Tree : The term 'Show-Tree' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At C:\Users\Admin\Documents\GitHub\powershell-scripts\ProjectSnapShot.ps1:6 char:9
+ $Tree = Show-Tree -ShowProperty -ShowLeaf | Where-Object {$_ -inotmat ...
+ ~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Show-Tree:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I checked the PSModulePath env variable and it is pointing to two valid loctions, one the std modules and the other pscx modules...
According to the docs, this should mean the module is loaded automatically.
Generally speaking, you can determine where you should install your
module by using one of the paths stored in the $ENV:PSModulePath
variable. Using one of these paths means that PowerShell can
automatically find and load your module when a user makes a call to it
in their code. If you store your module somewhere else, you can
explicitly let PowerShell know by passing in the location of your
module as a parameter when you call Install-Module.
How can I get this to work from the PS command prompt and when called from MSBuild?

Assuming you're having this problem when running the above code as a script, there are a number of things you can do, for example:
If you're only worried about this script running on your machine, you could pass Import-Module the explicit .psm1 path of the module file containing the function Show-Tree before you assign the $Tree variable.
If you're looking for more portability, you could copy the code defining the function Show-Tree from the .psm1 module file into this script, again, before you assign the $Tree variable.
If you're looking more to figure out why this is happening, here is the section on inheritance from the PowerShell help file about_Scopes:
Inheritance
A child scope does not inherit the variables, aliases, and functions from the parent scope. Unless an item is private, the child scope can view the items in the parent scope. And, it can change the items by explicitly specifying the parent scope, but the items are not part of the child scope.
However, a child scope is created with a set of items. Typically, it includes all the aliases that have the AllScope option. This option is discussed later in this topic. It includes all the variables that have the AllScope option, plus some variables that can be used to customize the scope, such as MaximumFunctionCount.

This problem was resolved after re-booting my computer. There were some other strange instabilities (new errors being thrown despite nothing having changed) that also disappeared after the re-boot.

I observed similar behaviour as well. Even if the GUI shows that PSMODULEPATH is ok, running processes (like explorer.exe) do not automatically pick up changes to the environment. Usually Log-Off/-On or a reboot helps.
Background:
When a process starts, the current environment is copied "into" the process. If a process starts another process, like if you start a program using Windows Explorer, the new child process gets a copy of the parent processes environment. Changes to the user or system environment variables are therefore not picked up by running processes unless the process explicitly listens to WM_SETTINGSCHANGED window message and reacts accordingly. From my experience not many processes do this correctly.

Related

Powershell: Why can't I add aliases in functions?

I tried adding several aliases in my powershell profile script. For organization reasons I wanted to keep those in a function which I would call at the end of said profile. I discovered that, while the function can be called from inside or outside the script without any problems, the aliases don't apply to my current powershell session. Only when I add them one at a time in the profile script, they would be usable.
This is my script as it is right now
function Populate-Aliases() {
New-Alias grep Select-String
New-Alias touch New-Item
New-Alias lsa dir -Force
}
Populate-Aliases
I'm also certain the script is executed when I create a new ps session, as is proven by inserting any output in the function. It's just the aliases that don't apply to my session.
I tried creating the aliases via function in the profile script, which didn't work. I also tried declaring a function from within the terminal as such:
function al(){New-Alias lsa dir -Force}
al
lsa
This did also not work which leads me to believe that I'm making some kind of mistake or creating aliases in functions is not supported (which I could not quite understand why that would be the case).
Creating an alias via New-Alias in the cli works without any problem. Also just adding the New-Alias statement to the profile script works, when it is not enclosed in a function.
-Scope
Specifies the scope in which this alias is valid. The default value is Local. For more information, see about_Scopes.
This means that, by default, the concerned alias is only available in the scope of the function:
function Test {
New-Alias Show Write-Host
Show 'This works'
}
Test
Show 'but this does not work'
Unless you will set the -scope to global:
function Test {
New-Alias -Scope Global Show Write-Host
Show 'This works'
}
Test
Show 'And this works too'
To complement iRon's helpful answer:
While a function name such as Populate-Aliases - or perhaps better, using an approved verb, Add-CustomAliases - does suggest modification of the caller's state, it is generally better to let the caller make the choice to have its state modified, by using . , the dot-sourcing operator, which executes the specified function or script directly in the caller's scope rather than in a child scope (as is the default and as happens when you use &, the call operator).
Thus, you could leave your function as-is and simply invoke it in your $PROFILE file as follows:
# Dot-source the function call, so that it runs directly in the current scope
# (which inside $PROFILE is the *global* scope), causing the aliases to
# become globally defined.
. Populate-Aliases
Note that this technique also allows you to out-source the alias definitions to a script file; say you place them in an CustomAliases.ps1 file alongside your $PROFILE file, you can then define them globally as follows:
# Ditto, via an external .ps1 file.
. $PSScriptRoot/CustomAliases.ps1
The only challenge is that not using . for invocation then becomes effectively a quiet no-op. The function's / script's comment-based help could make that clear, but you can also implement a runtime check to enforce dot-sourced invocation:
function Add-CustomAliases {
# Ensure that the function was invoked with dot-sourcing.
if ($MyInvocation.InvocationName -ne '.') {
throw "Please invoke this function dot-sourced."
}
New-Alias grep Select-String
New-Alias touch New-Item
New-Alias lsa dir -Force
}
. Add-CustomAliases # OK
Add-CustomAliases # Throws an error, due to using dot-sourcing.
Note: With the script-file implementation, an extended check is necessary for robustness (see this answer).
# Content of CustomAliases.ps1
# Ensure that the script was invoked with dot-sourcing.
if (-not ($MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -eq '')) {
throw "Please invoke this script dot-sourced."
}
New-Alias grep Select-String
New-Alias touch New-Item
New-Alias lsa dir -Force

How to execute a string in a variable in powershell

I have the following string
"C:\ProgramData\Package
Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe"
/uninstall
I need to execute the above string as below
First-line
cd C:\ProgramData\Package Cache\{6b95042e-f763-4850-9136-d004dd0d0a9b}
The second line (note there is no exe)
AzInfoProtection /uninstall
Variables are generally executed like below in PowerShell
Invoke-Expression $cmd
But how to split the above string into multiple lines for execution. Then I need to remove the quote and then exe.
It's a bit hard to understand the ask here but I think I follow. Let me know if I'm off base or misunderstanding what you're trying to do.
$commandString = '"C:\ProgramData\Package Cache{6b95042e-f763-4850-9136-d004dd0d0a9b}\AzInfoProtection.exe" /uninstall'
# Get command parent directory
if( $commandString -match '^".*?"' ) {
$runInDir = Split-Path -Parent $Matches[0]
}
# Change directories (use the location stack for easy traversal)
Push-Location $runInDir
# Run program
Invoke-Expression $commandString
# Change back to previous directory
Pop-Location
This works by checking if the string starts with a quote-enclosed string (escaped quotes should not need to be handled within filepaths), and if so gets the first match from the $Matches object. $Matches is an automatic variable which is populated whenever you get a $True result using the [-match operator][1]. With the command path extracted, we use Split-Path to get the parent container relative to the filepath.
Then use Push-Location to change directories. Push-Location works like Set-Location (aliased to cd) except it tracks the directories you leave and enter as a stack. Its sibling cmdlet Pop-Location is used further on to return to the previous location.
Finally, we use Invoke-Expression to run your command. After this completes use Pop-Location to return to the previous directory. Keep the following in mind:
You should take note that the use of Invoke-Expression is often implemented insecurely, and so you should consider heeding the warning on the documentation I've linked to and consider parameterizing your command if your $commandString is actually populated from a generated file, provided by a parameter, or another other outside source.
Note: You mentioned this in your question:
The second line (note there is no exe)
Windows doesn't care if you omit the extension for executable types when executing them. You can run AzInfoProtection.exe with or without the .exe at the end. So unless I'm missing something this detail doesn't have any bearing on how this code works.
To run the string you can pipe it to cmd to run it using:
$commandString | cmd

Powershell function call causes missing function error using powershell v7 on windows 10

I wrote a script to build all .net projects in a folder.
Issue
The issue is I am getting a missing function error when I call Build-Sollution.
What I tried
I made sure that function was declared before I used it so I am not really sure why it saids that it is not defined.
I am new to powershell but I would think a function calling another functions should work like this?
Thanks in advance!
Please see below for the error message and code.
Error Message
Line |
3 | Build-Sollution $_
| ~~~~~~~~~~~~~~~
The term 'Build-Sollution' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Build-Sollution:
Code
param (
#[Parameter(Mandatory=$true)][string]$plugin_path,
[string]$depth = 5
)
$plugin_path = 'path/to/sollutions/'
function Get-Sollutions {
Get-ChildItem -File -Path $plugin_path -Include *.sln -Recurse
}
function Build-Sollution($solution) {
dotnet build $solution.fullname
}
function Build-Sollutions($solutions) {
$solutions | ForEach-Object -Parallel {
Build-Sollution $_
}
}
$solutions_temp = Get-Sollutions
Build-Sollutions $solutions_temp
From PowerShell ForEach-Object Parallel Feature | PowerShell
Script blocks run in a context called a PowerShell runspace. The runspace context contains all of the defined variables, functions and loaded modules.
...
And each runspace must load whatever module is needed and have any variable be explicitly passed in from the calling script.
So in this case, the easiest solution is to define Build-Sollution inside Build-Sollutions
As for this...
I am new to powershell but I would think a function calling another
functions should work like this?
... you cannot use the functions until you load your code into memory. You need to run the code before the functions are available.
If you are in the ISE or VSCode, if the script is not saved, Select All and hit use the key to run. In the ISE use F8 Selected, F5 run all. In VSCode, F8 run selected, crtl+F5 run all. YOu can just click the menu options as well.
If you are doing this from the consolehost, the run the script using dot sourcing.
. .\UncToYourScript.ps1
It's ok to be new, we all started somewhere, but it's vital that you get ramped up first. so, beyond what I address here, be sure to spend time on Youtube and search for Beginning, Intermediate, Advanced PowerShell for videos to consume. There are tons of free training resources all over the web and using the built-in help files would have given you the answer as well.
about_Scripts
SCRIPT SCOPE AND DOT SOURCING Each script runs in its own scope. The
functions, variables, aliases, and drives that are created in the
script exist only in the script scope. You cannot access these items
or their values in the scope in which the script runs.
To run a script in a different scope, you can specify a scope, such as
Global or Local, or you can dot source the script.
The dot sourcing feature lets you run a script in the current scope
instead of in the script scope. When you run a script that is dot
sourced, the commands in the script run as though you had typed them
at the command prompt. The functions, variables, aliases, and drives
that the script creates are created in the scope in which you are
working. After the script runs, you can use the created items and
access their values in your session.
To dot source a script, type a dot (.) and a space before the script
path.
See also:
'powershell .net projects build run scripts'
'powershell build all .net projects in a folder'
Simple build script using Power Shell
Update
As per your comments below:
Sure the script should be saved, using whatever editor you choose.
The ISE does not use PSv7 by design, it uses WPSv5x and earlier.
The editor for PSv7 is VSCode. If you run a function that contains another function, you have explicitly loaded everything in that call, and as such it's available.
However, you are saying, you are using PSv7, so, you need to run your code in the PSv7 consolehost or VSCode, not the ISE.
Windows PowerShell (powershell.exe and powershell_ise.exe) and PowerShell Core (pwsh.exe) are two different environments, with two different executables, designed to run side-by-side on Windows, but you do have to explicitly choose which to use or write your code to branch to a code segment to execute relative to the host you started.
For example, let's say I wanted to run a console command and I am in the ISE, but I need to run that in Pwsh. I use a function like this that I have in a custom module autoloaded via my PowerShell profiles:
# Call code by console executable
Function Start-ConsoleCommand
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('scc')]
Param
(
[string]$ConsoleCommand,
[switch]$PoSHCore
)
If ($PoSHCore)
{Start-Process pwsh -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
Else {Start-Process powershell -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
}
All this code is doing is taking whatever command I send it and if I use the PoSHCore switch...
scc -ConsoleCommand 'SomeCommand' -PoSHCore
... it will shell out to PSCore, run the code, otherwise, it just runs from the ISE>
If you want to use the ISE with PSv7 adn not do the shell out thing, you need to force the ISE to use PSv7 to run code. See:
Using PowerShell Core 6 and 7 in the Windows PowerShell ISE

Remove alias in script

I can remove an alias like so:
Remove-Item Alias:wget
Then trying the alias gives the expected result:
PS > wget
wget : The term 'wget' is not recognized as the name of a cmdlet, function,
script file, or operable program.
However, if I put the same into a script,
PS > cat wget.ps1
Remove-Item Alias:wget
wget
it gives unexpected result
cmdlet Invoke-WebRequest at command pipeline position 1
Supply values for the following parameters:
Uri:
The following seems to work
If (Test-Path Alias:wget) {Remove-Item Alias:wget}
If (Test-Path Alias:wget) {Remove-Item Alias:wget}
wget
The first line removes the Script level alias, and the second removes the Global level alias. Note that both need to be tested, because if the Global doesn't exist the Script isn't created.
Also this misbehaves in that it modifies the parent without being dot sourced.
Alternatively you can just dot source your original and that would work
. .\wget.ps1
The reason behind this is scope...
There are four scopes: local, script, private, and global
The rules for variables, functions and aliases say that if they are not defined in the current scope then PowerShell will search the parent scopes.
The default for an alias is set to allscope, which make it visible from any child scopes and also they are inherited into any new child scope (I think that would be a fair definition).
Get-Alias -Name wget | select Name,options
Name Options
wget AllScope
Once you remove the alias that is the script scope (scope 0), it will then find the alias from the global/parent scope (scope 1).
When you dot source it, you are just saying to run the script in the calling/global scope in the first place, so you are removing the Global alias by default.
Try these...
E.g #1.
1.1) Remove the alias from the global scope.
Remove-Item -Path Alias:\wget
1.2) Create a new one (global scope) and make it private.
New-Alias -Name wget -Value dir -Scope private
1.3) Now the alias is not visible from the nested scope.
So try run the script and it will not be found.
E.g #2.
2.1) Remove the alias from the global scope.
Remove-Item -Path Alias:\wget
2.2) Create a new one and make it AllScope
New-Alias -Name wget -Value dir -Option AllScope
2.3) Now run your script and it will work fine (using the new alias dir from the parent scope)
You could try the same with variables as well. They should be easier to demo (and play around with) since you can more easily play with the scope parameter when you use New/Get/Set/Remove -scope 1 or scope 0 OR use the scope modifiers, for example, $global:a.
Get-Command -Noun variable -ParameterName scope
Clearly, some of the other answers are on-track and have identified the fact that the alias is present in several scopes.
But if you're trying to invoke wget.exe rather than Invoke-WebRequest (or wget.ps1 ) in some script, the simplest answer might be to put the ".exe" extension on the command name when you invoke it. That way you don't have to fiddle with the aliases.
It looks like when you run the script from the PowerShell console it inherits the alias drive from that session. So if wget is there it works, and if it's not it fails. If instead you run the script in a new process it gets removed normally.
Here's the code I used for my demo:
Remove-Item -Path Alias:\wget -Force
wget
Start-Sleep -Seconds 100
The start-sleep will keep your console from closing immediately. Try running that script in a new process. I used the run prompt and PowerShell itself by typing `powershell.exe -file pathtoscript '

Execute shortcuts like programs

Example: You have a shortcut s to SomeProgram in the current directory.
In cmd.exe, you can type s and it will launch the program.
In PowerShell, typing s gives:
The term 's' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
If you type s.lnk or SomeProgram, it runs the program just fine.
How can I configure PowerShell to execute shortcuts just like programs?
You can also invoke a shortcut by using the "invoke-item" cmdlet. So for example if you wanted to launch "internet explorer.lnk" you can type the following command:
invoke-item 'Internet Explorer.lnk'
Or you could also use the alias
ii 'internet explorer.lnk'
Another cool thing is that you could do "invoke-item t.txt" and it would automatically open whatever the default handler for *.txt files were, such as notepad.
Note If you want to execute an application, app.exe, in the current directory you have to actually specify the path, relative or absolute, to execute. ".\app.exe" is what you would need to type to execute the application.
On my Vista system typing S won't launch a lnk file unless I have the environment variable PATHEXT set with .lnk in the list. When I do. S will work in cmd.exe and I have to do .\S in powershell.
After adding ;.LNK to the end of my PATHEXT environment variable, I can now execute shortcuts even without the preceding ./ notation. (Thanks bruceatk!)
I was also inspired by Steven's suggestion to create a little script that automatically aliases all the shortcuts in my PATH (even though I plan to stick with the simpler solution ;).
$env:path.Split( ';' ) |
Get-ChildItem -filter *.lnk |
select #{ Name='Path'; Expression={ $_.FullName } },
#{ Name='Name'; Expression={ [IO.Path]::GetFileNameWithoutExtension( $_.Name ) } } |
where { -not (Get-Alias $_.Name -ea 0) } |
foreach { Set-Alias $_.Name $_.Path }
I don't believe you can. You might be better off aliasing commonly used commands in a script that you call from your profile script.
Example -
Set-Alias np c:\windows\notepad.exe
Then you have your short, easily typeable name available from the command line.
For one, the shortcut is not "s" it is "s.lnk". E.g. you are not able to open a text file (say with notepad) by typing "t" when the name is "t.txt" :) Technet says
The PATHEXT environment variable
defines the list of file extensions
checked by Windows NT when searching
for an executable file. The default value of PATHEXT is .COM;.EXE;.BAT;.CMD
You can dot-source as described by others here, or you could also use the invocation character "&". This means that PS treats your string as something to execute rather than just text. This might be more important in a script though.
I'd add that you should pass any parameters OUTSIDE of the quotes (this one bit me before) note that the "-r" is not in the quoted string, only the exe.
& "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe" -r | out-null
You can always use tab completion to type "s[TAB]" and press ENTER and that will execute it.