powershell use of where-object within script function not working - function

I'm trying to enable my powershell profile script with a function that will let me do literal and wildcard searches for the presence of a function in my current powershell terminal session.
Within my powershell profile script [ $env:userprofile\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 ] i've created the following function.
function Get-Fnc {
#Get-ChildItem function:\ | Where-Object { $_.Name -like "$args" }
Get-ChildItem function:\$args
}
Using the commented out line Get-ChildItem function:\ | Where-Object { $_.Name -like "$args" } doesn't work even though i can use that on the command line, e.g. Get-ChildItem function:\ | Where-Object { $_.Name -like "get-*" } it works as expected. Using the uncommented line Get-ChildItem function:\$args works both in the profile script function and the command line, e.g. Get-ChildItem function:\get-*.
Searching on net and in stackoverflow i've not been able to find any details on gotchas around making use of output piping | to another cmdlet and/or use of the Where-Object cmdlet within functions to determine how to make it work. Any insights on how to make output piped to where-object work in a script function when the same thing is known to work on command line?
Update
In addition to answer provided solutin was also able to use the following
function Get-Fnc {
$argsFncScope = $args # works because we make function scoped copy of args that flows down into Where-Object script block / stack frame
Write-Host "function scoped args assigned variable argsFncScope = $argsFncScope and count = $($argsFncScope.Count) and type = $($argsFncScope.GetType().BaseType)"
Get-ChildItem function:\ | Where-Object { $_.Name -like "$argsFncScope" }
}
Debug Output
get-fnc *-env
[DBG]: PS C:\Users\myusrn\Documents\WindowsPowerShell>
function scoped args assigned variable argsFncScope = *-env and count = 1 and type = array
[DBG]: PS C:\Users\myusrn\Documents\WindowsPowerShell>
CommandType Name Version Source
----------- ---- ------- ------
Function Get-Env

Each scriptblock has its own $args automatic variable.
function Get-Fnc {
Get-ChildItem function:\ | where-object { write-host $args;
$_.name -like "$args" } }
get-fnc get-*
# lots of empty lines
The $args for where-object is a $null array.
function Get-Fnc {
Get-ChildItem function:\ | where-object { $global:myargs = $args;
$_.name -like "$args" } }
get-fnc get-*
$myargs
$myargs.count
0
$myargs.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
You can however use the other version of where-object that doesn't use a scriptblock:
function Get-Fnc {
Get-ChildItem function:\ | where-object name -like $args }
get-fnc get-*
CommandType Name Version Source
----------- ---- ------- ------
Function Get-Fnc
See also: Why doesn't PowerShell Where-Object work when passing a variable?
I actually don't know a way to set $args within the where-object scriptblock, but here's another example of a scriptblock and $args. $args is actually an array.
& { $args[0]; $args[1] } hi there
hi
there

Related

Powershell - Reading multiple json files in a directory and output the data

I'm having to whip up a process that will read multiple json files created by another process.
I have code that can read a single file, but we're needing to process these results in bulk.
Here's my current code:
$json = Get-ChildItem $filePath -recurse | Where-Object { $_.LastWriteTime -gt [DateTime] $filesNewerThan } | ConvertFrom-Json
$json.delegates | foreach-Object {
foreach ($File in $_.files)
{
[PSCustomObject]#{
LastName = $_.lastName
ZipCode = $File.zipCode
BirthDate = $File.birthdate
Address = $File.Address}
}
}
Right now I'm getting an error about an "invalid JSON primitive" which what I'm guessing is an issue where I don't have "Get-Content" specified in my code.
Wondering what my issue is with my code.
ConvertFrom-Json currently (as of PowerShell 7.0) doesn't support file-path input, only file-content input (the actual JSON string), which means that you need to involve Get-Content:
$json = Get-ChildItem -File $filePath -Recurse |
Where-Object { $_.LastWriteTime -gt [DateTime] $filesNewerThan } |
ForEach-Object { Get-Content -Raw -LiteralPath $_.FullName | ConvertFrom-Json }

Access Object From JSON File in Powershell

I have a JSON file that I am reading in Powershell. The structure of the file is below.
[
["computer1", ["program1", versionX]],
["computer2", ["program2", versionY]],
["computer3", ["program3", "versionX"],
["program1", "versionZ"]
],
]
What I want in the program is use $env:computername and compare it with the computerX in the JSON file. If found a match, then iterate through and get the values of programName and ProgramVersion.
However, I don't know how to search through the objects and find ALL items under that.
This is what I have so far.
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$computername=$env:computername
$data = $rawData -match $computername
This gives me objects under it. But how do I iterate through and get individual values?
But don't know what I do after that.
To start you need to be using a valid JSON file
{
"computer1": {
"program1": "versionX"
},
"computer2": {
"program2": "versionY"
},
"computer3": {
"program3": "versionX",
"program1": "versionZ"
}
}
Then you can access the PSObject Properties
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$rawData.PsObject.Properties |
Select-Object -ExpandProperty Name |
ForEach-Object { IF ($_ -eq $env:COMPUTERNAME) {
Write-Host "Computer Name : " $_
Write-Host "Value : " $rawData."$_"
}
}
EDIT for Computer, Program, and Version as separate values
psobject.Properties.Name will give all the program names.
psobject.Properties.Name[0] will give the first program name.
psobject.Properties.value[0] will give the first program version value.
You need to increment the value to get second value, you can also use -1 as a shortcut for the last value.
$rawData = Get-Content -Raw -Path "file.json" | ConvertFrom-Json
$rawData.PsObject.Properties |
Select-Object -ExpandProperty Name |
ForEach-Object { IF ($_ -eq $env:COMPUTERNAME) {
$Computer = $_
$Values = $rawData.$_
}
}
$Computer
$Values.psobject.Properties
$Values.psobject.Properties.Name
$Values.psobject.Properties.Name[0]
$Values.psobject.Properties.value[0]
$Values.psobject.Properties.Name[1]
$Values.psobject.Properties.value[1]
You could also use the program name
$Values.program1
$Values.program2
$Values.program3

PowerShell: Function doesn't have proper return value

I wrote a powershell script to compare the content of two folders:
$Dir1 ="d:\TEMP\Dir1"
$Dir2 ="d:\TEMP\Dir2"
function Test-Diff($Dir1, $Dir2) {
$fileList1 = Get-ChildItem $Dir1 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
$fileList2 = Get-ChildItem $Dir2 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
if($fileList1.Count -ne $fileList2.Count) {
Write-Host "Following files are different:"
Compare-Object -ReferenceObject $fileList1 -DifferenceObject $fileList2 -Property Name -PassThru | Format-Table FullName
return $false
}
return $true
}
$i = Test-Diff $Dir1 $Dir2
if($i) {
Write-Output "Test OK"
} else {
Write-Host "Test FAILED" -BackgroundColor Red
}
If I set a break point on Compare-Object, and I run this command in console, I get the list of differences. If I run the whole script, I don't get any output. Why?
I'm working in PowerGUI Script Editor, but I tried the normal ps console too.
EDIT:
The problem is the check on the end of the script.
$i = Test-Diff $Dir1 $Dir2
if($i) {
Write-Output "Test OK"
...
If I call Test-Diff without $i = check, it works!
Test-Diff returns with an array of objects and not with an expected bool value:
[DBG]: PS D:\>> $i | ForEach-Object { $_.GetType() } | Format-Table -Property Name
Name
----
FormatStartData
GroupStartData
FormatEntryData
GroupEndData
FormatEndData
Boolean
If I comment out the line with Compare-Object, the return value is a boolean value, as expected.
The question is: why?
I've found the answer here: http://martinzugec.blogspot.hu/2008/08/returning-values-from-fuctions-in.html
Functions like this:
Function bar {
[System.Collections.ArrayList]$MyVariable = #()
$MyVariable.Add("a")
$MyVariable.Add("b")
Return $MyVariable
}
uses a PowerShell way of returning objects: #(0,1,"a","b") and not #("a","b")
To make this function work as expected, you will need to redirect output to null:
Function bar {
[System.Collections.ArrayList]$MyVariable = #()
$MyVariable.Add("a") | Out-Null
$MyVariable.Add("b") | Out-Null
Return $MyVariable
}
In our case, the function has to be refactored as suggested by Koliat.
An alternative to adding Out-Null after every command but the last is doing this:
$i = (Test-Diff $Dir1 $Dir2 | select -last 1)
PowerShell functions always return the result of all the commands executed in the function as an Object[] (unless you pipe the command to Out-Null or store the result in a variable), but the expression following the return statement is always the last one, and can be extracted with select -last 1.
I have modified the bit of your script, to make it run the way you want it. I'm not exactly sure you would want to compare files only by the .Count property though, but its not within the scope of this question. If that wasn't what you were looking after, please comment and I'll try to edit this answer. Basically from what I understand you wanted to run a condition check after the function, while it can be easily implemented inside the function.
$Dir1 ="C:\Dir1"
$Dir2 ="C:\Users\a.pawlak\Desktop\Dir2"
function Test-Diff($Dir1,$Dir2)
{
$fileList1 = Get-ChildItem $Dir1 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
$fileList2 = Get-ChildItem $Dir2 -Recurse | Where-Object {!$_.PsIsContainer} | Get-Item | Sort-Object -Property Name
if ($fileList1.Count -ne $fileList2.Count)
{
Write-Host "Following files are different:"
Compare-Object -ReferenceObject $fileList1 -DifferenceObject $fileList2 -Property FullName -PassThru | Format-Table FullName
Write-Host "Test FAILED" -BackgroundColor Red
}
else
{
return $true
Write-Output "Test OK"
}
}
Test-Diff $Dir1 $Dir2
If there is anything unclear, let me know
AlexP

Hashtables from ConvertFrom-json have different type from powershells built-in hashtables, how do I make them the same?

I have a json file (test.json) that looks something like this:
{
"root":
{
"key":"value"
}
}
I'm loading it into powershell using something like this:
PS > $data = [System.String]::Join("", [System.IO.File]::ReadAllLines("test.json")) | ConvertFrom-Json
root
----
#{key=value}
I'd like to be able to enumerate the keys of the 'hashtable' like object that is defined by the json file. So, Ideally, I'd like to be able to do something like:
$data.root.Keys
and get ["key"] back. I can do this with the built-in hashtables in powershell, but doing this with a hashtable loaded from json is less obvious.
In troubleshooting this, I've noticed that the fields returned by ConvertFrom-json have different types than those of Powershell's hashtables. For example, calling .GetType() on a built-in hashtable makes shows that it's of type 'Hashtable':
PS > $h = #{"a"=1;"b"=2;"c"=3}
PS > $h.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
doing the same for my json object yields PSCustomObject:
PS > $data.root.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
Is there any way to cast or transform this object into a typical powershell Hashtable?
Here's a quick function to convert a PSObject back into a hashtable (with support for nested objects; intended for use with DSC ConfigurationData, but can be used wherever you need it).
function ConvertPSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = #(
foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
)
Write-Output -NoEnumerate $collection
}
elseif ($InputObject -is [psobject])
{
$hash = #{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
}
$hash
}
else
{
$InputObject
}
}
}
The ConvertFrom-Json cmdlet gives you a custom object so you have to access them using dot notation rather than as a subscript. Usually you would know what fields you expect in the JSON so this is actually more useful in general than getting a hash table. Rather than fighting the system by converting back to a hash table, I suggest you work with it.
You can use select with wildcard property names to get at the properties:
PS D:\> $data = #"
{
"root":
{
"key":"value", "key2":"value2", "another":42
}
}
"# | ConvertFrom-Json
PS D:\> $data.root | select * | ft -AutoSize
key key2 another
--- ---- -------
value value2 42
PS D:\> $data.root | select k* | ft -AutoSize
key key2
--- ----
value value2
and Get-Member if you want to extract a list of property names that you can iterate over:
PS D:\> ($data.root | Get-Member -MemberType NoteProperty).Name
another
key
key2
Putting that into a loop gives code like this:
PS D:\> foreach ($k in ($data.root | Get-Member k* -MemberType NoteProperty).Name) {
Write-Output "$k = $($data.root.$k)"
}
key = value
key2 = value2
The example was for a relatively shallow source object (not nested objects in the properties).
Here's a version that will go 2 levels deep into the source object, and should work with your data:
$data = #{}
foreach ($propL1 in $x.psobject.properties.name)
{
$data[$propL1] = #{}
foreach ($propL2 in $x.$propL1.psobject.properties.name)
{
$data[$PropL1][$PropL2] = $x.$propL1.$propL2
}
}
$data.root.keys
key
I put this together to handle nested json to hashtables
function ConvertJSONToHash{
param(
$root
)
$hash = #{}
$keys = $root | gm -MemberType NoteProperty | select -exp Name
$keys | %{
$key=$_
$obj=$root.$($_)
if($obj -match "#{")
{
$nesthash=ConvertJSONToHash $obj
$hash.add($key,$nesthash)
}
else
{
$hash.add($key,$obj)
}
}
return $hash
}
I have only tested with 4 levels but recursive until it has complete hashtable.
#Duncan: If you need to use JSON Input for an command expecting a hashmap though (eg SET-ADUSER), try something like this:
function SetADProperties{
param($PlannedChanges)
$UserName = $PlannedChanges.Request.User
$Properties = #{}
foreach ($key in ($PlannedChanges.SetADProperties | Get-Member -MemberType NoteProperty).Name)
{
$Properties[$key] = $PlannedChanges.SetADProperties.$key
}
# Call Set-ADUser only once, not in a loop
Set-ADUser -Identity $UserName -Replace $Properties
}
$content = Get-Content -encoding UTF8 $FileName
$PlannedChanges = $content | ConvertFrom-Json
SetADProperties $PlannedChanges | Write-Output
Example JSON:
{"SetADProperties":{"postalCode":"01234","l":"Duckburg","employeenumber":"012345678"},
"Request":{"Action":"UserMove","User":"WICHKIND","Change":"CH1506-00023"}}

Powershell Function Variables

I'm writing a script to find local admins on machines in a specific OU. I've created two functions to preform this task, each function by itself is working fine, but when I combine the two I am not getting any result. Anyone know what I'm doing wrong here?
Function GetCompList{
Get-ADObject -Filter { ObjectClass -eq "computer" } -SearchBase "OU=Resources,DC=Contoso,DC=LOCAL" `
| Select-Object Name
}
Function Admin_Groups{
foreach($i in GetCompList){
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
New-Object -TypeName PSCustomObject -Property #{
UserName = $_.Name -join ''
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
}
}
$Object |? {$_.Groups -match "Administrators*"}
}
}
Admin_Groups
Your GetCompList function is returning a collection of objects. You're probably getting this when you run the one function:
Name
------
Comp1
Comp2
Comp3
In the foreach loop of Admin_Groups, you're using the output of GetCompList as an array of primitives - just a list of names, not a bunch of objects. So, you have two options:
Change the select-object name in GetCompList to select-object -expandproperty Name to get a simple array of names
In Admin_Groups, change each reference to $i in the body of the foreach loop to $i.Name. Since you're using it within a string, it's a little ugly to do that.
In this particular example, my preference would be option #1, making that function:
Function GetCompList{
Get-ADObject -Filter { ObjectClass -eq "computer" } -SearchBase "OU=Resources,DC=Contoso,DC=LOCAL" | Select-Object -expandproperty Name
}
I would also suggest that you rename your functions to match the Verb-Noun convention of PowerShell, and use one of the approved verbs from get-verb.
Get-CompList
Get-AdminGroups
Failing that, at least make your function names consistent - either use the _ to separate the words in the names, or don't. Don't mix & match.