Outputing $_ hashtable variables in a write-warning using a ForEach-Object - powershell

so I'm trying to use this load of powershell from here: Getting the current hash key in a ForEach-Object loop in powershell
$myHash.keys | ForEach-Object {
Write-Host $_["Entry 1"]
}
It's all working but I want to output the value as part of another string, i.e.:
$results.notvalid | ForEach-Object {
write-warning 'Incorrect status code returned from $_["url"],
code: $_["statuscode"]'
}
so I want the output to be:
Incorrect status code returned from www.xxxx.com, code: 404
but instead I get
Incorrect status code returned from $_["url"], code: $_["statuscode"]
what am I missing?
BTW, this works if I just do
$results.notvalid | ForEach-Object {
write-warning $_["url"]
}
I then get
www.xxxx.com

I prefer using format strings over embedded expressions (i.e. $()). I find it more readable. Also, PowerShell creates properties on a hashtable for each key, so instead of indexing (i.e. $_['url']) you can $_.url.
$results.notvalid |
ForEach-Object { 'Incorrect status code returned from {0}, code: {1}'
-f $_.url,$_.statuscode } |
Write-Warning

You must put string in double quote instead of single quote if you want variables to be read. Also, in this spot you are not accessing directly a variable value, so you should use $() to be sure code to be evaluated in advance.
$results.notvalid | ForEach-Object {
write-warning "Incorrect status code returned from $($_['url']), code: $($_['statuscode'])"
}

Related

Where-Object not finding match but it should

I have Code that imports and excel spreadsheet like this, and I've used this for other excel files fine:
$importedExcel = Import-Excel -Path $errorCodeListFilePath -StartRow $startRow
return $importedExcel
The returned map is stored like this:
[Object[178]]
...
[2]:
Contents:"some string"
ErrorCode Upper: 12
ErrorCode Lower: "3A"
Error Item: "some more info"
Recovery: "important recovery info"
Recovery Method Info: "A12"
...
Where sometimes ErrorCode Upper is a string and sometimes it's a number, and same for Lower.
$deviceErrDescMap = Process_ErrorDescMap -errorCodeListFilePath $errorCodeListFile
And the data looks as expected, shown above (excel returned in code snippet above).
For this excel file, I think it's confused because some data is an int and some is a string, so I think my where-object is missing the match. For this one, I had to split a hex number into the first two and second two digits, because the spreadsheet splits them across two columns.
$sdkNum = "0x123A"
$sdkNumArr = #($sdkNum -split 'x')
$sdkNumArr1 = $sdkNumArr[1] -split ''
$sdkNumUpper = "$($sdkNumArr1[1])$($sdkNumArr1[2])" #12 ..naming is counter-intuitive
$sdkNumLower = "$($sdkNumArr1[3])$($sdkNumArr1[4])" #3A
$deviceErrDescRow = $deviceErrDescMap | Where-Object {([string]'ErrorCode Upper' -eq $sdkNumUpper)} #returns no results
When I hover over $sdkNumUpper" I see "12", so it looks like a string. Can't I cast the spreadsheet/map content to string like I'm showing? I'm having trouble finding this info with an internet search. But something must be wrong with my Where-Object, because even though I see the row is there, it's returning null for $deviceErrDescRow.
The problem is the syntax of your Where-Object call:
... | Where-Object {([string]'ErrorCode Upper' -eq $sdkNumUpper)}
You're comparing the string literal 'ErrorCode Upper' to $sdkNumUpper, not the value of the .ErrorCode Upper property.
If you're using Where-Object with a script block ({ ... }), you need to refer to the input object at hand via the automatic $_ variable
... | Where-Object { $sdkNumUpper -eq $_.'ErrorCode Upper' }
Note that by placing the string-valued $sdkNumUpper on the LHS, the property value on the RHS is implicitly converted to a string too, if needed.
By contrast, when you use the syntactically easier simplified syntax, the property access is always the LHS, and only the property name must be specified, which is implicitly applied to the input object object at hand (in other words: application to $_ is implied).
# Note: 'ErrorCode Upper' binds to parameter -Property, $sdkNumUpper to -Value
# Equivalent to:
# ... | Where-Object { $_.'ErrorCode Upper' -eq $sdkNumUpper }
... | Where-Object 'ErrorCode Upper' -eq $sdkNumUpper

Foreach with Where-Object not yielding correct results

I have the following code:
$ErrCodes = Get-AlarmIDs_XML -fileNamePath $Paper_Dialog_FullBasePath
$excelDevice = Get_ErrorCodes -errorCodeListFilePath $outFilePath -ws "DEVICE COMPONENT MAP"
foreach ($errCode in $ErrCodes | Where-Object{$excelDevice.Common_Alarm_Number -eq $errCode.Value })
{
#$dataList = [System.Collections.Generic.List[psobject]]::new()
#$resultHelp = [System.Collections.Generic.List[psobject]]::new()
Write-Host "another:"
$err = $errCode.Value #get each error code to lookup in mdb file
$key = $errCode.Key
Write-Host $err
...
}
But it's definitely getting in the foreach loop when it shouldn't.
My intention is to use the foreach, and if it has a value in the $ErrCodes, then it should continue with the code that follows.
Let me know if you need to see the Functions that do the file reads, but the data structures look like this:
$excelDevice:
[Object[57]]
[0]:#{Common_Alarm_Number=12-2000}
[1]:#{Common_Alarm_Number=12-5707}
[2]:#{Common_Alarm_Number=12-9}
[3]:#{Common_Alarm_Number=12-5703}
...
$ErrCodes:
[Object[7]]
[0]:#{Key=A;Value=12-5702}
[1]:#{Key=B;Value=12-5704}
[2]:#{Key=C;Value=12-5706}
[3]:#{Key=D;Value=12-5707}
...
So we only care about the ones in $ErrCodes that are also in $excelDevice.
When I step through the code, it's getting into the foreach code for 12-5702 for some reason, when it shouldn't be there (prints 12-5702 to screen). I know I wouldn't want 12-5702 to be used because it isn't in $excelDevice list.
How would I get that Where-Object to filter out $ErrCodes that aren't in $excelDevice list? I don't want to process error codes that don't have data for this device.
Right now you're testing whether any of the values in $excelDevice.Common_Alarm_Number (which presumably evaluates to an array) is exactly the same value as all the values in $errCodes.Value - which doesn't make much sense.
It looks like you'll want to test each error code for whether it is contained in the $excelDevice.Common_Alarm_Number list instead. Use $_ to refer to the individual input items received via the pipeline:
foreach ($errCode in $ErrCodes | Where-Object{ $excelDevice.Common_Alarm_Number -contains $_.Value }) { ... }

Return boolean from string search

I'm trying to return TRUE from searching Get-ComplianceSearch's output for 'Completed'. My code below is a simple wait loop. But I don't think I'm returning the value correctly because the loop never finishes. I'm fairly new to PowerShell. Please assist or direct.
I'm using Powershell Core 7.1. There are no errors but the Search-String condition never returns TRUE.
try {
$timer = [Diagnostics.Stopwatch]::StartNew()
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and (-not (Get-ComplianceSearch -
Identity $searchName | Select-String 'Completed' -SimpleMatch -Quiet))) {
Start-Sleep -Seconds $RetryInterval
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds, 0)
Write-Verbose -Message "Still waiting for action to complete after [$totalSecs]
seconds..."
}
$timer.Stop()
if ($timer.Elapsed.TotalSeconds -gt $Timeout) {
throw 'Action did not complete before timeout period.'
} else {
Write-Verbose -Message 'Action completed before timeout period.'
}
} catch {
Write-Error -Message $_.Exception.Message
}
(This is the expected output of the command Get-ComplianceSearch)
Okay, you don't want to use Select-String here (although you can, see #mklement0's helpful answer, looking at object properties is usually preferred). That is returning an object and you want to check the Status property for "Completed". Make the following change to the -not subexpression:
(-not (Get-ComplianceSearch -Identity $searchName | Where-Object {
$_.Status -eq 'Completed'
}))
The above can be on one line but I broke it up for readability.
Basically, Select-String looks for content in strings. If you are looking for a particular value of an object property however, you can use Where-Object to test for a condition and return any objects matching that condition. In this case, we want to return any object that have a Status of 'Completed', so we can negate that in the if statement.
You (or others) might be wondering how this works since Where-Object returns matching objects, but not booleans. The answer is "truthiness". PowerShell objects are "truthy", which means anything can be evaluated as a [bool].
The following values evaluate to $false in most cases. I've included some gotchas to watch out for when relying on "truthy" values:
A numeric value of 0
A string value of 0 evaluates as $true
Empty arrays
Empty strings
A whitespace-only string or strings consisting only of non-printable characters evaluates as $true
$false
A string value of False evaluates as $true
Most everything else will evaluate to $true. This is also why comparison operators are syntactically optional when checking whether a variable is $null or not. Although there are times when an explicit value check is a good idea as comparison operators compare the actual values instead of only whether the variable "is" or "isn't".
How does this apply to the expression above then? Simple. if statements, always treat the condition expression as a [bool], no conversion required. In addition, logical operators and conditional operators also imply a boolean comparison. For example, $var = $obj assigns $obj to $var, but$var = $obj -eq $obj2 or $var = $obj -and $obj2 will assign $true or $false.
So knowing the above, if Where-Object returns nothing, it's $false. If it returns a tangible object, it's $true.
Bender the Greatest's helpful answer shows a better alternative to using Select-String, because OO-based filtering that queries specific properties is always more robust than searching string representations.
That said, for quick-and-dirty interactive searches, being able to search through a command's formatted display output can be handy, and, unfortunately, Select-String does not do that by default.
As for what you tried:
To make your Select-String work, you need to insert Out-String -Stream before the Select-String call, so as to ensure that the for-display representation is sent through the pipeline, line by line.
# `oss` can be used in lieu of `Out-String -Stream` in PSv5+.
# `sls` can be used in lieu of `Select-String`.
Get-ComplianceSearch | Out-String -Stream | Select-String 'Completed' -SimpleMatch -Quiet
Note:
If you want to search a for-display representation other than the default one, you can insert a Format-* cmdlet call before the Out-String -Stream segment; e.g.
Get-Item / | Format-List * | Out-String -Stream | Select-String ... would search through a list representation of all properties of the object output by Get-Item.
Perhaps surprisingly, Select-String does not search an input object's for-display representation, as you would see it in the console, using the rich formatting provided by PowerShell's display-formatting system.
Instead, it performs simple .ToString() stringification, whose results are often unhelpful and cannot be relied upon to include the values of properties. (E.g.,
#{ foo = 'bar' } | Select-String foo does not work as intended; it is equivalent to
#{ foo = 'bar' }.ToString() | Select-String foo and therefore to
'System.Collections.Hashtable' | Select-String foo
Arguably, Select-String should always have defaulted to searching through the input objects' formatted string representations:
That there is demand for this behavior is evidenced by the fact that PowerShell versions 5 and above (both editions) ship with the oss convenience function, which is a wrapper for Out-String -Stream.
GitHub issue #10726 asks that the current behavior of Select-String be changed to search the for-display string representations by default.

Incrementing variables in powershell

I'm new to PowerShell and am trying to create a script that goes through a csv file (simple name,value csv) and loads each new line in it as a variable and then runs a function against that set of variables.
I've had success at getting it to work for 1 variable by using the following code snippet:
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
}
My csv looks like this:
name,value
RegKey1,"Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\LanmanWorkstation"
Basically it's a list of registry keys each named as RegKey# and then the path of that reg key is the intended value of the variable.
I'm currently playing around with the "Test-Path" cmdlet that just prints out true/false if the passed reg-key exists and then just prints out some text based on if it found the reg key or not.
That snippet looks like so:
Test-Path $RegKey1
IF ($LASTEXITCODE=0) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
This works fine however what I'm trying to achieve is for powershell to run this cmdlet against each of the lines in the csv file - basically checking each reg key in it and then doing whatever specified to it.
What I'm trying to avoid is declaring hundreds of variables for every regkey I plan on using but instead have this one function that just runs through the csv and every time it runs, it increments the number next to the variable's name - RegKey1,RegKey2,RegKey3 etc.
Let me know if there's a way to do this in powershell or a better way of approaching this altogether. I also apologize in advance if I've not provided enough info, please do let me know.
You need to place your if statement in the Foreach-Object loop. This will also only work, if your variable all get the same name of $RegKey. To incriment, you may use the for loop.
Import-Csv -Path C:\something\mylist.csv | ForEach-Object {
New-Variable -Name $_.Name -Value $_.Value -Force
IF (Test-Path $RegKey1) {
Write-Output "It worked"
}
else {
Write-Output "It didn't work"
}
}
The if statement returns a boolean value of $true, or $false. So theres no need to use $LastExitCode by placing the Test-Path as the condition to evaluate for.
Alternatively, you can use the Foreach loop to accomplish the same thing here:
$CSV = Import-Csv -Path C:\something\mylist.csv
Foreach($Key in $CSV.Value){
$PathTest = Test-Path -Path $Key
if($PathTest) {
Write-Output "It worked"
} else {
Write-Output "It didn't work"
}
}
By iterating(reading through the list 1 at a time) through the csv only selecting the value(Reg Path), we can test against that value by assigning its value to the $PathTest Variable, to be evaluated in your if statement just like above; theres also no need to assign it to a variable and we can just use the Test-Path in your if statement like we did above as well for the same results.

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.