I'm brand new to powershell, and I'm finding a very odd behavior when I try and pass a parameter into a function:
Code
Param(
[Parameter(Mandatory=$true)][string]$vhdSourceDir,
[Parameter(Mandatory=$true)][string]$vhdDestinationDir,
[Parameter(Mandatory=$true)][array]$hypervisorList,
[int]$vhdKeep = 10
)
function getDirectoryBuildNumber ($dir) {
# Returns a number like 627c6ddeb8776914 from a path like:
# c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir | Where-Object {$_.Name -match "^[A-Za-z0-9]{16}$" } | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
function findBoxFile ($dir, $build) {
echo "looking for build $build at $dir "
echo "the dir is $dir and the build is $build"
# e.g c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir\$build\packer\windows\box\hyperv\ | Where-Object Extension -in '.box' | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
Function Main ()
{
$THEBUILD=getDirectoryBuildNumber($vhdSourceDir)
echo "THEBUILD is $THEBUILD"
findBoxFile($vhdSourceDir,$THEBUILD)
#echo "BOXFILE is $BOXFILE"
}
main
Problem
Here are the parameters that I call the script with:
.\boxMove.ps1 -vhdSourceDir C:\BuildAgent\work -vhdDestinationDir e:\ -hypervisorList 'foobar'
Here is the output it generates
THEBUILD is 527c6ddeb8776914
looking for build at C:\BuildAgent\work 527c6ddeb8776914
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Get-ChildItem : Cannot find path 'C:\BuildAgent\work 527c6ddeb8776914\packer\windows\box\hyperv\' because it does not exist.
The parameters show up out of order. For example, the phrase 'looking for build should appear like so"
looking for build 527c6ddeb8776914 at C:\BuildAgent\work
but it shows up as
looking for build at C:\BuildAgent\work 527c6ddeb8776914
Also the phrase 'the dir is..' should read
the dir is C:\BuildAgent\work and the build is 527c6ddeb8776914
but it reads as
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Why is powershell not printing the strings in order?
When you pass parameters to functions do not encapsulate them in parentheses and where multiple parameters are being passed, they should not be comma separated, instead a space should be used to separate them.
For instance:
findBoxFile($vhdSourceDir,$THEBUILD)
should read:
findBoxFile $vhdSourceDir $THEBUILD
Then you'll find that this removes the issue you were encountering with the incorrectly ordered output.
Firstly, clean up your function definitions. Instead of
function (params){code}
you should use
function
{
Param ($Param1,$param2)
code
}
Also, when accepting an array as a param, instead of using [array], specify the array type, (probably string in your case), so that would be (line 4)
[Parameter(Mandatory=$true)][string[]]$hypervisorList,
It's hard do say exactly why the script fails, but I'd start by cleaning up the obvious issues and test again.
Related
What I want to achieve:
Create a list of files, show their name and version and sort alphabetically.
My attempt:
class File_Information
{
[ValidateNotNullOrEmpty()][string]$Name
[ValidateNotNullOrEmpty()][string]$FileVersion
}
$FileList = New-Object System.Collections.Generic.List[File_Information]
foreach ($file in Get-Item("*.dll")){
$C = [File_Information]#{
Name = $file.Name
FileVersion = $file.VersionInfo.FileVersion
}
$FileList = $FileList.Add($C)
}
foreach ($file in Get-Item("*.exe")){
$C = [File_Information]#{
Name = $file.Name
FileVersion = $file.VersionInfo.FileVersion
}
$FileList = $FileList.Add($C)
}
Write-Output $FileList | Sort-Object -Property "Name" | Format-Table
My Powershell version:
Prompt> Get-Host | Select-Object Version
Version
-------
5.1.19041.1320
My problems and/or questions (first is the major question, second and third are optional):
I don't know how to initialise a list of custom objects (I've been looking on the site, but this question only gives the possibility to initialise with an existing value, while I want to initialise to an empty list).
The error I receive is:
You cannot call a method on a null-valued expression.
At line:... char:...
+ $FileList = $FileList.Add($C)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Currently I ask for the same thing twice: I have tried Get-Item(*.dll,*.exe), Get-Item(*.dll;*.exe) and Get-Item(*.dll|*.exe) but none of them worked.
Is it even possible to search for different patterns?
Until now I am struggling just to get Write-Output $FileList working. Are the other two commands (Sort-Object -Property "Name", Format-Table) correct?
Why use a class and a List object for that at all?
You can just use Select-Object on the files of interest you gather using Get-ChildItem and output the needed properties there:
# capture the resulting objects in a variable $result
$result = Get-ChildItem -Path 'X:\path\to\the\files' -File -Include '*.dll','*.exe' -Recurse |
Select-Object Name, #{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}
# output on screen
$result | Sort-Object Name | Format-Table -AutoSize
To use two different name patterns on Get-ChildItem, you need to also add switch -Recurse, or append \* to the path.
If you do not want recursion, you will need to add a Where-Object clause like:
$result = Get-ChildItem -Path 'X:\path with spaces' | Where-Object {$_.Extension -match '\.(dll|exe)'} |
Select-Object Name, #{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}
Your code actually works fine, with two small exceptions.
Problem #1: overly restrictive property attributes can cause errors
First off, in your custom class, you should be very sure you want to append this attribute [ValidateNotNullOrEmpty()], as it will throw a non-terminating error when you try to new up an instance of a class if any one of the properties with this attribute are null.
For instance, on my PC, I have a dll or two with empty file.VersionInfo.FileVersion properties. When that happens, PowerShell will fail to create the object, and then also fail trying to add the object to the list because there is no object. Very confusing.
Fix : remove the attribute of ValidateNotNullOrEmpty unless you're 100% positive this info is present.
Next up, the real problem here is that in each of your for-each loops, you're accidentally breaking your FileList object which you correctly setup in your code as it is already.
Problem #2: Small syntax issue is causing your list to be nulled out, causing errors for every op after the first.
The issue happens in this line below, which I will explain.
$FileList = $FileList.Add($C)
The List.Add() method's return type is either a void or an integer (it's only an int when you provide a generic object, if you provide the type specified for the list, in your case being your custom class of File_Information, it provides a VOID.)
Void is the same as null. We can confirm that the output of .Add is the same as $null with the following code.
PS> $FileList.Add($c) -eq $null
True
You can see the output for the various ways of calling .Add() in detail by just typing out the method name with no parameters, like this.
PS> $FileList.Add
OverloadDefinitions
-------------------
void Add(File_Information item) #< the one you're using!
void ICollection[File_Information].Add(File_Information item)
int IList.Add(System.Object value)
So in effect your for each loops are adding an item to the list, then resaving the variable $FileList to the output of the .Add method, which is a void, or null in other words. You're killing your own list!
We don't need to resave $FileList as we go, since the purpose of a list is to be populated in code, and the only thing we have to do is to call the .Add() method.
Fix: stop killing your list object in your loops by changing these lines in your code.
#before
$FileList = $FileList.Add($C)
#after
$FileList.Add($C)
I'm trying to create a report file which lists all files in my Git repository with these columns:
Name
Size
Last Modified
Git Commit
The first 3 are no problem:
gci -File -Recurse | Select-Object -Property #{
label = 'Size(KB)'
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, LastWriteTime, FullName
However, retrieving the git commit requires running Git, a native command.
I've tried, among others:
gci -File -Recurse | Select-Object -Property #{
label = 'Git commit'
expr = { Invoke-Expression "git log -1 --format:format=`"%s`" -- $($_.FullName)" }
},
#{
label = 'Size(KB)'
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, LastWriteTime, FullName
But it just gets stuck.
Does anyone know how to do this??
P.S.
All the flags and options to Git doesn't matter for the manner of sake, I just copied what I already did.
As #mklement0 suggested in the comments, the issue was just that your formatting for the --format command was off just enough to cause a problem.
You had:
--format:format="%s" # won't work
--format=format:"%s" # works
So to fix it, we just swap in the right format, giving us this command with the output below.
gci -File | Select-Object -Property #{
label = 'Git commit'
expr = { Invoke-Expression "git log -1 --format=format:`"%s`" $($_.Name)" }
},
#{
label = 'Size(KB)'
expr = { [string]::Format("{0:0.00}", $_.Length/1KB) }
}, LastWriteTime, FullName
Git commit Size(KB) LastWriteTime FullName
---------- -------- ------------- --------
applied gitingore 6.07 5/11/2020 11:22:06 AM C:\git\ClientFaux\.gitignore
cleanin up layout 1.40 10/9/2020 3:20:33 PM C:\git\ClientFaux\ClientFaux.sln
Create LICENSE (#25) 34.98 7/13/2020 9:55:00 AM C:\git\ClientFaux\LICENSE
Update README.md (#27) 3.37 7/13/2020 9:55:00 AM C:\git\ClientFaux\README.md
31.13 7/13/2020 9:55:27 AM C:\git\ClientFaux\UpgradeLog.htm
FoxDeploy's helpful answer contains the crucial pointer, but let me offer a (corrected) PowerShell-idiomatic reformulation of your code.
Get-ChildItem -File -Recurse | Select-Object -Property #{
label = 'Git commit'
expr = { git log -1 --format=format:%s -- $_.FullName }
},
#{
label = 'Size(KB)'
expr = { '{0:0.00}' -f ($_.Length/1KB) }
}, LastWriteTime, FullName
Note:
Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program or PowerShell script, so the above uses direct invocation of git.
As mentioned, --format:format="%s" is syntactically incorrect and should be --format=format:"%s" (I've omitted the " above, which PowerShell would strip behind the scenes anyway).
-f, the format operator, is used as the PowerShell-idiomatic alternative to directly calling the underlying .NET API, System.String.Format.
Calling native executables in calculated properties:
Generally, note that there's nothing fundamentally special about calling native executables from the expression script block ({ ... }) of a calculated property.
Specifically, the following considerations apply:
In PowerShell, stdout output from native executables is invariably converted to text ([string] instances). If the output comprises multiple lines, the property values becomes a (regular [object[]]) array containing strings.
If the native executable produces no stdout output, the value of the property is "nothing", i.e. the so-called "Automation Null" value ([System.Management.Automation.Internal.AutomationNull]::Value) that in most context behaves like $null.
Any stderr output from the native executable is quietly discarded. (Analogously, errors from PowerShell commands or expressions in expression script blocks are quietly ignored.)
Therefore, in order to troubleshoot an expression script block, execute it stand-alone, via a ForEach-Object command (whose built-in alias is %), so as to surface any errors; e.g.:
# Uses broken `git` syntax; note the resulting error message.
PS> (Get-ChildItem -File)[0] | % { git log -1 --format:format=%s -- $_.FullName }
fatal: unrecognized argument: --format:format=%s
As for what you tried:
Because your git command was syntactically incorrect, git only produced stderr output, which PowerShell then ignored, as explained above.
Thus, your Git commit properties ended up containing "nothing", which simply renders blank in output formatting.
Is there any command to list all functions I've created in a script?
Like i created function doXY and function getABC or something like this.
Then I type in the command and it shows:
Function doXY
Function getABC
Would be a cool feature^^
Thanks for all your help.
You can have PowerShell parse your script, and then locate the function definitions in the resulting Abstract Syntax Tree (AST).
Get-Command is probably the easiest way to access the AST:
# Use Get-Command to parse the script
$myScript = Get-Command .\path\to\script.ps1
$scriptAST = $myScript.ScriptBlock.AST
# Search the AST for function definitions
$functionDefinitions = $scriptAST.FindAll({
$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]
}, $false)
# Report function name and line number in the script
$functionDefinitions |ForEach-Object {
Write-Host "Function '$($_.Name)' found on line $($_.StartLineNumber)!"
}
You can also use this to analyze the functions' contents and parameters if necessary.
Where your script is named things.ps1, something like...
cat ./things.ps1 | grep function
For MacOS/Linux or...
cat ./things.ps1 | select-string function
For Windows.
This is a built-in feature as shown in the PowerShell help files.
About_Providers
Similar questions have been asked before. So, this is a potential duplicate of:
How to get a list of custom Powershell functions?
Answers... Using the PSDrive feature
# To get a list of available functions
Get-ChildItem function:\
# To remove a powershell function
# removes `someFunction`
Remove-Item function:\someFunction
Or
Function Get-MyCommands {
Get-Content -Path $profile | Select-String -Pattern "^function.+" | ForEach-Object {
[Regex]::Matches($_, "^function ([a-z.-]+)","IgnoreCase").Groups[1].Value
} | Where-Object { $_ -ine "prompt" } | Sort-Object
}
Or this one
Get List Of Functions From Script
$currentFunctions = Get-ChildItem function:
# dot source your script to load it to the current runspace
. "C:\someScript.ps1"
$scriptFunctions = Get-ChildItem function: | Where-Object { $currentFunctions -notcontains $_ }
$scriptFunctions | ForEach-Object {
& $_.ScriptBlock
}
As for this...
Thanks, this is kind of what i want, but it also shows functions like
A:, B:, Get-Verb, Clear-Host, ...
That is by design. If you want it another way, then you have to code that.
To get name of functions in any script, it has to be loaded into memory first, then you can dot source the definition and get the internals. If you just want the function names, you can use regex to get them.
Or as simple as this...
Function Show-ScriptFunctions
{
[cmdletbinding()]
[Alias('ssf')]
Param
(
[string]$FullPathToScriptFile
)
(Get-Content -Path $FullPathToScriptFile) |
Select-String -Pattern 'function'
}
ssf -FullPathToScriptFile 'D:\Scripts\Format-NumericRange.ps1'
# Results
<#
function Format-NumericRange
function Flush-NumberBuffer
#>
This function will parse all the functions included in a .ps1 file, and then will return objects for each function found.
The output can be piped directly into Invoke-Expression to load the retuned functions into the current scope.
You can also provide an array of desired names, or a Regular Expression to constrain the results.
My use case was I needed a way for loading individual functions from larger scripts, that I don't own, so I could do pester testing.
Note: only tested in PowerShell 7, but I suspect it will work in older versions too.
function Get-Function {
<#
.SYNOPSIS
Returns a named function from a .ps1 file without executing the file
.DESCRIPTION
This is useful where you have a blended file containing functions and executed instructions.
If neither -Names nor -Regex are provided then all functions in the file are returned.
Returned objects can be piped directly into Invoke-Expression which will place them into the current scope.
Returns an array of objects with the following
- .ToString()
- .Name
- .Parameters
- .Body
- .Extent
- .IsFilter
- .IsWorkFlow
- .Parent
.PARAMETER -Names
Array of Strings; Optional
If provided then function objects of these names will be returned
The name must exactly match the provided value
Case Insensitive.
.PARAMETER -Regex
Regular Expression; Optional
If provided then function objects with names that match will be returned
Case Insensitive
.EXAMPLE
Get all the functions names included in the file
Get-Function -name TestA | select name
.EXAMPLE
Import a function into the current scope
Get-Function -name TestA | Invoke-Expression
#>
param (
$File = "c:\fullpath\SomePowerShellScriptFile.ps1"
,
[alias("Name", "FunctionNames", "Functions")]
$Names
,
[alias("NameRegex")]
$Regex
) # end function
# get the script and parse it
$Script = Get-Command /Users/royomi/Documents/dev/javascript/BenderBot_AI/Import-Function.ps1
$AllFunctions = $Script.ScriptBlock.AST.FindAll({$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]}, $false)
# return all requested functions
$AllFunctions | Where-Object { `
( $Names -and $Names -icontains $_.Name ) `
-or ( $Regex -and $Names -imatch $Regex ) `
-or (-not $Names -and -not $Regex) `
} # end where-object
} # end function Get-Function
Suppose I have scripts AAA.ps1 and script BBB.ps1. Assume they are in the same location (directory).
Script BBB.ps1 defines a function BFunc which I want to invoke from AAA.ps1
How do I modify AAA.ps1 so that it can define-and-use BFunc?
As a test I have tried the following two commands in a script.
& "$PSScriptRoot\BBB.ps1"
get-childitem function:* | Select-Object Name | where { $_ -match "BFunc" }
If the above worked then I should see some output that BFunc is defined but nothing comes out.
Ah, sometimes it is too simple. It turns out that what PS expects is that BBB.ps1 be included into AAA.ps1. This means that the AAA.ps1 file could look like this:
. "$PSScriptRoot\BBB.ps1"
get-childitem function:* | Select-Object Name | where { $_ -match "BFunc" }
And that works!
I have many oracle forms in one folder and I want to compile those forms through frmcmp command in powershell script.
I have written a powershell script which is following
$module="module="
get-childitem "C:\forms\fortest" -recurse |
where { $_.extension -eq ".fmb" } |
foreach {
C:\Oracle\Middleware\Oracle_FRHome1\BIN\frmcmp $module $_.FullName userid=xyz/xyz#xyz Output_File=C:\forms\11\common\fmx\$_.BaseName+'.fmx'
}
but this one is not working. i am new in powershell.
but when I try to compile a single form through command prompt its working like following.
frmcmp module=C:\forms\src\xyz.fmb userid=xyz/xyz#xyz Output_File=C:\forms\11\common\fmx\xyz.fmx
When you want to use variables inside a string in PowerShell you have different options. To start with, you will always need to use " as opposed to ' to wrap the string, if you want variables in your string.
$myVariable = "MyPropertyValue"
Write-Host "The variable has the value $MyVariable"
The above code would yield the output:
The variable has the value MyPropertyValue
If you want to use a property of a variable (or any expression) and insert it into the string, you need to wrap it in the string with $(expression goes here), e.g.
$MyVariable = New-Object PSObject -Property #{ MyPropertyName = 'MyPropertyValue' }
# The following will fail getting the property since it will only consider
# the variable name as code, not the dot or the property name. It will
# therefore ToString the object and append the literal string .MyPropertyName
Write-Host "Failed property value retrieval: $MyVariable.MyPropertyName"
# This will succeed, since it's wrapped as code.
Write-Host "Successful property value retrieval: $($MyVariable.MyPropertyName)"
# You can have any code in those wrappers, for example math.
Write-Host "Maths calculating: 3 * 27 = $( 3 * 27 )"
The above code would yield the following output:
Failed property value retrieval: #{MyPropertyName=MyPropertyValue}.MyPropertyName
Successful property value retrieval: MyPropertyValue
Maths calculating: 3 * 27 = 81
I generally try to use the Start-Process cmdlet when I start processes in PowerShell, since it gives me the possibility of additional control over the process started. This means that you could use something similar to the following.
Get-ChildItem "C:\forms\fortest" -Filter "*.fmb" -recurse | Foreach {
$FormPath = $_.FullName
$ResultingFileName = $_.BaseName
Start-Process -FilePath "C:\Oracle\Middleware\Oracle_FRHome1\BIN\frmcmp.exe" -ArgumentList "module=$FormPath", "userid=xyz/xyz#xyz", "Output_File=C:\forms\11\common\fmx\$ResultingFileName.fmx"
}
You could also add the -Wait parameter to the Start-Process command, if you want to wait with compilation of the next item until the current compilation has completed.