Powershell Select-Object: Executing native commands in calculated property - powershell

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.

Related

Powershell Different color in strings with echo in array

I trying to write a script, which show iis pools state with a different color.
And I can't understand why script coloring in one color all strings when I use echo.
Here script:
$pools = invoke-command -session $session -scriptblock {Import-Module WebAdministration; Get-ChildItem IIS:\AppPools | Where {$_.Name -like "*abc*"}}
$poolsShow = $pools | Select-Object -Property name, state
$poolsShow | ForEach-Object {
if($_.state -eq "Started") {
$Host.UI.RawUI.ForegroundColor = "Green";
echo $_;
[Console]::ResetColor();
}
if($_.state -eq "Stopped") {
$Host.UI.RawUI.ForegroundColor = "Red";
echo $_;
[Console]::ResetColor();
}
}
It is work if I go through the $pools, but if I select name and state via Select-Object - all strings are coloring in the color of the last service.
I have tried via Write-Host - and it's worked, but I didn't find a way, how to format output in one table with a headers only at first line and with the same width in every string.
You can take a similar approach as the one proposed in this answer, the only difference would be that the ANSI Escape Sequences are prepended to the property values of the objects created by Select-Object. Helpful answer provided by #mklement0 in the same question provides more details on this.
function status {
$ansi = switch($args[0]) {
Stopped { "$([char] 27)[91m" }
Started { "$([char] 27)[32m" }
}
$ansi + $args[0]
}
Invoke-Command -Session $session -ScriptBlock {
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | Where-Object { $_.Name -like "*abc*" }
} | Select-Object Name, #{ N='Status'; E={ status $_.State }}
A demo using custom objects:
0..5 | ForEach-Object {
[pscustomobject]#{
Name = "Test $_"
Status = ('Started', 'Stopped')[$_ % 2]
}
} | Select-Object Name, #{ N='Status'; E={ status $_.Status }}
To complement Santiago's helpful answer:
As for what you tried:
echo in PowerShell is a built-in alias for Write-Output, which does not print directly to the console - instead, it prints to the success output stream.
If the success output stream isn't captured or redirected in a given command, it is eventually printed to the console, after undergoing for-display formatting by PowerShell's formatting system.
Because your output objects have 4 or fewer properties, PowerShell applies tabular formatting by default; that is, the Format-Table cmdlet is implicitly used, which has a perhaps surprising implication:
So as to allow determining suitable column widths for the table, a 300-millisecond delay is introduced during which the objects are internally cached and analyzed.
While this behavior is helpful in principle, it has surprising side effects, notably in that direct-to-host output and output from other streams then can appear out of order; a simple example: [pscustomobject] #{ foo = 1 }; Write-Host 'Why am I printing first?? - see this answer for background information.
Therefore, the formatted table's rows only started printing after that delay, so your attempt to control their color one by one with ForEach-Object was ineffective.
As an aside: In PowerShell (Core) 7.2+ there's an additional consideration: formatted output now applies its own coloring by default, as controlled by the .OutputRendering property of the $PSStyle preference variable.
Santiago's answer bypasses this problem by using a calculated property to color individual property values rather than trying to control the coloring of the already-formatted representation of the object.
If you want a prepackaged, general-purpose solution, you can use the Out-HostColored function from this Gist (authored by me), which in your case would make the solution as simple as piping your objects to Out-HostColored.ps1 #{ Started = 'Green'; Stopped = 'Red' }:
# Download and define function `Out-HostColored` on demand (will prompt).
# To be safe, inspect the source code at the specified URL first.
if (-not (Get-Command -ErrorAction Ignore Out-HostColored1)) {
$gistUrl = 'https://gist.github.com/mklement0/243ea8297e7db0e1c03a67ce4b1e765d/raw/Out-HostColored.ps1'
if ((Read-Host "`n====`n OK to download and define function ``Out-HostColored```n from Gist ${gistUrl}?`n=====`n(y/n)?").Trim() -notin 'y', 'yes') { Write-Warning 'Aborted.'; exit 2 }
Invoke-RestMethod $gistUrl | Invoke-Expression 3>$null
if (-not ${function:Out-HostColored}) { exit 2 }
}
# Emit sample objects and color parts of their formatted representation
# based on regex patterns.
0..5 | ForEach-Object {
[pscustomobject]#{
Name = "Test $_"
State = ('Started', 'Stopped')[$_ % 2]
}
} |
Out-HostColored.ps1 #{ Started = 'Green'; Stopped = 'Red' }
Output:
Add -WholeLine if you want to color matching lines in full.
The hashtable maps search text patterns to colors.
Whenever a pattern is found in the formatted representations of the input objects, it is colored using the specified color.
Note that this means that finding what to color is purely based on string parsing, not on OOP features (such as checking specific properties).
Note that the hashtable keys are regexes by default, unless you also specify
-SimpleMatch.
Thus you could easily make the above more robust, if needed, such as replacing Started = with '\bStarted\b' = in order to only match full words.

Windows version of Linux's 'which'? [duplicate]

How do I ask PowerShell where something is?
For instance, "which notepad" and it returns the directory where the notepad.exe is run from according to the current paths.
The very first alias I made once I started customizing my profile in PowerShell was 'which'.
New-Alias which get-command
To add this to your profile, type this:
"`nNew-Alias which get-command" | add-content $profile
The `n at the start of the last line is to ensure it will start as a new line.
Here is an actual *nix equivalent, i.e. it gives *nix-style output.
Get-Command <your command> | Select-Object -ExpandProperty Definition
Just replace with whatever you're looking for.
PS C:\> Get-Command notepad.exe | Select-Object -ExpandProperty Definition
C:\Windows\system32\notepad.exe
When you add it to your profile, you will want to use a function rather than an alias because you can't use aliases with pipes:
function which($name)
{
Get-Command $name | Select-Object -ExpandProperty Definition
}
Now, when you reload your profile you can do this:
PS C:\> which notepad
C:\Windows\system32\notepad.exe
I usually just type:
gcm notepad
or
gcm note*
gcm is the default alias for Get-Command.
On my system, gcm note* outputs:
[27] » gcm note*
CommandType Name Definition
----------- ---- ----------
Application notepad.exe C:\WINDOWS\notepad.exe
Application notepad.exe C:\WINDOWS\system32\notepad.exe
Application Notepad2.exe C:\Utils\Notepad2.exe
Application Notepad2.ini C:\Utils\Notepad2.ini
You get the directory and the command that matches what you're looking for.
Try this example:
(Get-Command notepad.exe).Path
My proposition for the Which function:
function which($cmd) { get-command $cmd | % { $_.Path } }
PS C:\> which devcon
C:\local\code\bin\devcon.exe
A quick-and-dirty match to Unix which is
New-Alias which where.exe
But it returns multiple lines if they exist so then it becomes
function which {where.exe command | select -first 1}
I like Get-Command | Format-List, or shorter, using aliases for the two and only for powershell.exe:
gcm powershell | fl
You can find aliases like this:
alias -definition Format-List
Tab completion works with gcm.
To have tab list all options at once:
set-psreadlineoption -editmode emacs
This seems to do what you want (I found it on http://huddledmasses.org/powershell-find-path/):
Function Find-Path($Path, [switch]$All = $false, [Microsoft.PowerShell.Commands.TestPathType]$type = "Any")
## You could comment out the function stuff and use it as a script instead, with this line:
#param($Path, [switch]$All = $false, [Microsoft.PowerShell.Commands.TestPathType]$type = "Any")
if($(Test-Path $Path -Type $type)) {
return $path
} else {
[string[]]$paths = #($pwd);
$paths += "$pwd;$env:path".split(";")
$paths = Join-Path $paths $(Split-Path $Path -leaf) | ? { Test-Path $_ -Type $type }
if($paths.Length -gt 0) {
if($All) {
return $paths;
} else {
return $paths[0]
}
}
}
throw "Couldn't find a matching path of type $type"
}
Set-Alias find Find-Path
Check this PowerShell Which.
The code provided there suggests this:
($Env:Path).Split(";") | Get-ChildItem -filter notepad.exe
Try the where command on Windows 2003 or later (or Windows 2000/XP if you've installed a Resource Kit).
BTW, this received more answers in other questions:
Is there an equivalent of 'which' on Windows?
PowerShell equivalent to Unix which command?
If you want a comamnd that both accepts input from pipeline or as paramater, you should try this:
function which($name) {
if ($name) { $input = $name }
Get-Command $input | Select-Object -ExpandProperty Path
}
copy-paste the command to your profile (notepad $profile).
Examples:
❯ echo clang.exe | which
C:\Program Files\LLVM\bin\clang.exe
❯ which clang.exe
C:\Program Files\LLVM\bin\clang.exe
I have this which advanced function in my PowerShell profile:
function which {
<#
.SYNOPSIS
Identifies the source of a PowerShell command.
.DESCRIPTION
Identifies the source of a PowerShell command. External commands (Applications) are identified by the path to the executable
(which must be in the system PATH); cmdlets and functions are identified as such and the name of the module they are defined in
provided; aliases are expanded and the source of the alias definition is returned.
.INPUTS
No inputs; you cannot pipe data to this function.
.OUTPUTS
.PARAMETER Name
The name of the command to be identified.
.EXAMPLE
PS C:\Users\Smith\Documents> which Get-Command
Get-Command: Cmdlet in module Microsoft.PowerShell.Core
(Identifies type and source of command)
.EXAMPLE
PS C:\Users\Smith\Documents> which notepad
C:\WINDOWS\SYSTEM32\notepad.exe
(Indicates the full path of the executable)
#>
param(
[String]$name
)
$cmd = Get-Command $name
$redirect = $null
switch ($cmd.CommandType) {
"Alias" { "{0}: Alias for ({1})" -f $cmd.Name, (. { which $cmd.Definition } ) }
"Application" { $cmd.Source }
"Cmdlet" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"Function" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"Workflow" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"ExternalScript" { $cmd.Source }
default { $cmd }
}
}
Use:
function Which([string] $cmd) {
$path = (($Env:Path).Split(";") | Select -uniq | Where { $_.Length } | Where { Test-Path $_ } | Get-ChildItem -filter $cmd).FullName
if ($path) { $path.ToString() }
}
# Check if Chocolatey is installed
if (Which('cinst.bat')) {
Write-Host "yes"
} else {
Write-Host "no"
}
Or this version, calling the original where command.
This version also works better, because it is not limited to bat files:
function which([string] $cmd) {
$where = iex $(Join-Path $env:SystemRoot "System32\where.exe $cmd 2>&1")
$first = $($where -split '[\r\n]')
if ($first.getType().BaseType.Name -eq 'Array') {
$first = $first[0]
}
if (Test-Path $first) {
$first
}
}
# Check if Curl is installed
if (which('curl')) {
echo 'yes'
} else {
echo 'no'
}
You can install the which command from https://goprogram.co.uk/software/commands, along with all of the other UNIX commands.
If you have scoop you can install a direct clone of which:
scoop install which
which notepad
There also always the option of using which. there are actually three ways to access which from Windows powershell, the first (not necessarily the best) wsl -e which command (this requires installation of windows subsystem for Linux and a running distro). B. gnuwin32 which is a port of several gnu binaries in .exe format as standle alone bundled lanunchers option three, install msys2 (cross compiler platform) if you go where it installed in /usr/bin you'll find many many gnu utils that are more up-to-date. most of them work as stand alone exe and can be copied from the bin folder to your home drive somewhere amd added to your PATH.
There also always the option of using which. there are actually three ways to access which from Windows powershell
The first, (though not the best) is wsl(windows subsystem for linux)
wsl -e which command
This requires installation of windows subsystem for Linux and a running distro.
Next is gnuwin32 which is a port of several gnu binaries in .exe format as standle alone bundled lanunchers
Third, install msys2 (cross compiler platform) if you go where it installed in /usr/bin you'll find many many gnu utils that are more up-to-date. most of them work as stand alone exe and can be copied from the bin folder to your home drive somewhere amd added to your PATH.

select-string not taking variable in powershell

I have stored a MAC address as a string in a variable:
$macaddress= "1234567"
I'm trying to store the output from a command in another variable:
$testing = { arp -a; | select-string $macaddress }
Write-Host $testing
If the command is executed in PowerShell, I do not see the value of $macaddress. It displays as a '$macaddress' in the screen instead of its value "1234567".
Please help me set the value of $macaddress correctly.
The problem is not how you define variable $macaddress, but in how you try to capture command output.
Remove the enclosing { ... } and the ;:
$testing = arp -a | select-string $macaddress
As for what you tried:
{ ... } creates a script block, which is a piece of source code (loosely speaking) for later execution (e.g., with operators . or &).
If you pass a script block to Write-Host - whose use you should generally avoid, by the way - it is converted to a string, and the string representation is the literal contents of the script block between { and } - that's why you saw $macaddress appear (unexpanded) in your output.
; terminates a command, and it is only necessary if you place multiple commands on a single line.
A pipeline is still considered a single command, even though it is composed of multiple sub-commands; do not attempt to use ; in a pipeline - you'll break it (and, in fact, even your script-block-creating command would break).
Try it this way:
$macAddress = "00-01-02-03-04"
arp -a | Select-String $macAddress
If you want to extract the IP address related to the MAC address, you can do this:
$macAddress = "00-01-02-03-04"
arp -a | Select-String ('\W*((?:[0-9]{1,3}\.){3}(?:[0-9]{1,3}))\W+(' +
$macAddress + ')') | ForEach-Object { $_.Matches[0].Groups[1].Value }

Powershell prints variables out of order

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.

compile oracle form using powershell script

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.