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

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

Related

Alternatives to invoke-expression

I have this function:
function traced()
{
write-host "$args"
invoke-expression -Command "$args"
}
and I use it in several places like traced cp "$($_.FullName)" (join-path $directory $newfile) so that I have a log of all of the places that get copied (or removed, or whatever)
But when the directory contains spaces and dashes, it results in invoke-expression throwing.
I guess I could just define traced-cp and traced-rm, but if I have a lot of functions I want to trace, what's the generic answer? I just want a function that prints, then evaluates, the exact command its given. From what I understand, the & operator isn't what I want here-- It won't work for shell builtins.
[...] so that I have a log of all of the places that get copied (or removed, or whatever)
I'd strongly recommend you use transcript logging for this!
You can start a transcript interactively with the Start-Transcript cmdlet, but if you want to keep a transcript of every single instance of PowerShell you launch by default, I'd suggest turning it on by default!
Open the local policy editor (gpedit.msc) on your Windows box and navigate to:
Computer Configuration
> Administrative Templates
> Windows Components
> Windows PowerShell
Select the policy setting named "Turn on PowerShell Transcription", set it to Enabled, and optionally configure your preferred output directory (defaults to your home folder).
This way, you'll always have a full transcript of your interactions in PowerShell :)
Consider using argument splatting to build your command instead of building a string-based command with Invoke-Expression. I also don't know where you heard that & doesn't work with shell built-ins but it works with both commands and cmdlets.
Here is the official Microsoft documentation on splatting in Powershell.
This approach also eliminates the difficulty in crafting a command string correctly, having to escape characters, and dealing with path spaces - using splatting with named or positional arguments takes care of most of this for you.
I would suggest using -verbose with copy-item or remove-item, and also -passthru with copy-item.

Module returns different result from the script version [Test-Path]

introduction
I've written my first PowerShell script aimed for retrieving detailed information from a Windows Setup ISO file. Once the basic features achieved, I've started to convert the ps1 script into a psm1 module. I hoped the result would be the module just work like the script but I'm facing issue I'm not able to solve.
You can download my work here, script version and module (roughly translated to English from French).
I successfully installed the module in PSModulePath in:
[Environment]::GetFolderPath("mydocuments")\WindowsPowerShell\Modules
Command usage is very simple. You call it like that:
WinIsoInfo [[-Path] <String>] [<CommonParameters>]
Help is provided by module: man WinIsoInfo
Usage Example:
WinIsoInfo -Path "E:\Win 10\Installation\ISO\Windows 10 x64 fr.iso"
The ps1 script version is the exact same code as the psm1 module but there are commands examples at the end of the file that you can un-comment and edit before running the script.
Current Status
All the tests are and need to be run as admin, in console or PowerShell ISE.
The ps1 script works as expected but the psm1 module doesn't produce the same result.
At line 108 of the code, there is a Test-Path in a Switch statement:
{(Test-Path "$wimPath\sources\install.wim") -or (Test-Path "$wimPath\sources\install.esd")}
In the ps1 script, this Test-Path return True and user get the expected info.
But in the psm1, it seems to return False since Switch statement jump to the next test after this one. So at the end the user gets that the ISO doesn't contain windows setup. I can assure that the Test-Path should return True because I manually checked it while the function was paused by breakpoints.
Hint and lead
There are 2 cases where I manage to get the module work as expected. But only using in PowerShell ISE, NOT in console.
Using Automatic Variable $? in console pane while debugging module
Step to reproduce:
PowerShell ISE is not running.
Open PowerShell ISE as admin.
In console pane, run import-module Get-WinIsoInfo -Force -Global -Verbose or import-module -path X:\Path\To\Modules\Get-WinIsoInfo -Force -Global -Verbose
In console pane, run WinIsoInfo -Path "X:\path\to\AnyWindowsSetup.iso"
In my case, at this point, the command returns there is no Windows Setup in ISO file.
Now open the Get-WinIsoInfo.psm1 and put a breakpoints anywhere between line 90-108.
do step 4 again
While the script is paused at breakpoints, run $? in the console pane then press F10 then F5
And "voilĂ  !" the module return the expected result and will keep working but only during PowerShell ISE session and inside PowerShell ISE. Command run in console still won't work. And the next time I run PowerShell ISE, the module won't find the setup image path again.
Previously run the ps1 script version in PowerShell ISE
Step to reproduce:
PowerShell ISE is not running.
Open PowerShell ISE as admin.
In console pane, run import-module Get-WinIsoInfo -Force -Global -Verbose or import-module -path X:\Path\To\Modules\Get-WinIsoInfo -Force -Global -Verbose
In console pane, run WinIsoInfo -Path "X:\path\to\AnyWindowsSetup.iso"
In my case, at this point, the command returns there is no Windows Setup in ISO file.
Now open the Get-WinIsoInfo.ps1 script, edit a valid command at the end of the code then press F5 to run it.
Note: Since the command in script has the same name as the module previously imported, at this point I don't know if the triggered function is the one from the ps1 script or the one from the module. Tell me if you know.
The script returns the expected result as Windows Setup info.
Close the ps1 file (it is no longer needed in PowerShell ISE for the next to work)
do step 4 again
And "voilĂ  !" the module return the expected result and will keep working but only during PowerShell ISE session and inside PowerShell ISE. Command run in console still won't work. And the next time I run PowerShell ISE, the module won't find the setup image path again.
Conclusion
After the Hint and lead tests, I found out that they were some differences from modules imported in session before and after success. These key modules loaded by PowerShell ISE are Storage and Microsoft.WSMan.Management. I thought I found the solution and added this line to manifest:
RequiredModules = #("Storage";"Microsoft.PowerShell.Management";"Microsoft.PowerShell.Security";"Microsoft.PowerShell.Utility";"Microsoft.WSMan.Management")
I added all the modules that was present after the module works as expected, just to be sure.
I did the same for assemblies but 2 of them could not be imported: Microsoft.Management.Infrastructure.UserFilteredExceptionHandling and Microsoft.Management.Infrastructure.resources
Resulting in this new manifest line:
RequiredAssemblies = #("Microsoft.Management.Infrastructure.Native";"Microsoft.WSMan.Runtime";"System.Security")
Unfortunately, it seems it is not enough to solve the issue.
Maybe other things has to be imported or it's a wrong lead.
I really hope you could reproduce the bug or at least I hope the Hint and lead section will lead you to find the cause and a solution. I'm too novice to understand why this happens on my system.
My setup uses PowerShell v5.0 with Win 8.1 Pro.

Is it possible to start Powershell ISE with specified (not default) profile?

I want to have several (more than one) PowerShell profiles which will create different environments.
More specifically I need way for start separate PowerShell ISE for work with TFS and other PowerShell ISE instance for regular work. 'TFS' environment require loading some additional snappins, modules, modify prompt and so on. I do not want all this stuff will be executed for regular PowerShell ISE sessions but only when I want to.
I found that I can automatically load arbitrary script through command line parameter -File, but it does not executed automatically..
I do it by creating a shortcut for PowerShell ISE with a default directory :
In the default Directory (here called D:\TFS) I create a .PS1 file called local_profile.ps1.
In the beginning of the current profile file (C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1) I add :
# Try to load local profile
Get-ChildItem "local_profile.ps1" -ErrorAction SilentlyContinue | %{.$_}
You just have to add your initialization code to D:\TFS\local_profile.ps1.
powershell ISE has a profile too.
Probably is something like:
E:\Users\UserName\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1
Or you can open powershell ise and look at $profile variable.
After locate your profile file, write your modules import and custom scripts in it.

How to use a Jenkins variable in my Powershell Script

I have written a simple script via PowerShell to gather some files and zip them into one folder, lets call it Script.ps1. I want to make the script run every time Jenkins does a new build, however I also would like the name of the zip file to be the BUILD_NUMBER.
How can I create a variable in PowerShell that is Jenkins's current build number? As of the moment I am calling the Script.ps1 in the execute shell section of configuration.
I'm not familiar with Jenkins, but I believe BUILD_NUMBER is an environment variable.
To access it in PowerShell, use $env:BUILD_NUMBER
E.g. If using 7-zip
7z.exe a "$env:BUILD_NUMBER.zip" C:\Build\Path
You can add arguments to your Script.ps1. Just use Param at the top of the script:
Param( $BuildNumber ) #must be first statement in script
# your current script goes here
then you can call the script passing BUILD_NUMBER as argument from Jenkins. Refer to this question for calling Powershell script with argument.
You could also use the Powershell Plugin, see here.
It allows you to enter PowerShell commands directly in a text box in Jenkins.
Then you can use the available environment variables (see docu of the plugin). The usage is a little cryptic:
(Get-Content ./Sources/SlmMachineControl/GUI/Project1.rc).replace("`"FileVersion`", `"1.0.0.0`"" ,"`"FileVersion`" `"2.3.$($env:BUILD_NUMBER).0`"") | Set-Content ./Sources/SlmMachineControl/GUI/Project1.rc
Also note the escaping of the double-quotes. Took me quite a while :)

My PowerShell script only works when running from ISE

I can't post all of the script contenet, but the basic idea is that it downloads JSON and converts it to objects using the ConvertFrom-Json cmdlet. Some objects are filtered out, and the rest are written to an XML/XLS document (in the Excel 2003 format). This file is then attached to an email and sent to various people.
The problem I'm having is that it only works when run from the Powershell ISE. Once I try setting up a scheduled task, calling it from cmd, or even calling it from powershell, the attached file is completely empty. It is as if some functions do not run (the one that loops through and creates all rows).
I can continue to run from ISE for the time being, but the idea of this script is to send out an automatic email that will require no intervention. Any ideas as to what could be causing this?
You need to run the script "dot sourced"
which can be done like this
powershell.exe -noexit -file c:\test.ps1
or
pwershell.exe -noexit ". c:\test.ps1"
See this link under the -File section
http://technet.microsoft.com/en-us/library/hh847736.aspx
Based on the answer from the comments to the original post, it seems that the script was (unexpectedly?) working in the ISE because of the bug/quirk/feature that allows scripts run in the ISE to be aware of variables used in the console window and vice-versa.
This can often cause logic problems or unexpected results when a script runs with a variable already defined (and not initialized carefully in the script).
Ways to avoid this problem:
Try to test your code in as clean an environment as possible.
http://powershell.com/cs/blogs/tips/archive/2015/02/12/getting-a-clean-powershell-environment.aspx
To make sure a script runs in a completely clean test environment, you
could of course restart the PowerShell ISE. A more convenient way is
to open another PowerShell tab: in the PowerShell ISE, choose File/New
PowerShell Tab.
Use Set-StrictMode 2 in your script to catch undefined variables, etc.
https://technet.microsoft.com/en-us/library/hh849692.aspx
Set-StrictMode -Version 2.0
Prohibits references to uninitialized variables (including uninitialized variables in strings).
Prohibits references to non-existent properties of an object.
Prohibits function calls that use the syntax for calling methods.
Prohibits a variable without a name (${}).
I have had this problem be for and for me executing the scrip using single-threaded function from powershell worked.
You could also try some other options, go to this link to find more info.
Example
powershell.exe -noexit c:\test.ps1 -sta
Change the missing variable or function to global.
function global:mycall{}
Start your Script with:
[cmdletbinding()]