Not sure if I am doing something stupid or if it is a "feature" of PowerShell.
Taking the following example code snippet:
[array]$Strings = #(
"This is an example string"
"This another example string test"
"This is something else fun"
"Not in list"
)
$oData = #()
Foreach($string in $strings)
{
$split = if($string.substring(0,4) -eq "This"){$String.Split(" ")}
$oData += [pscustomobject]#{
Part1 = $Split[0]
Part2 = $Split[1]
Part3 = $Split[2]
Part4 = $split[3]
Part5 = $split[4]
}
}
$oData
This throws the error Cannot index into a null array which is expected as the fourth member of the array "Strings" is not in the list so it cannot be indexed. Fair enough. To mitigate this, I though of the following modification:
$oData = #()
Foreach($string in $strings)
{
$split = if($string.substring(0,4) -eq "This"){$String.Split(" ")}
$oData += [pscustomobject]#{
Part1 = if($Split){$split[0]}
}
}
Which works, until I go to add Part2 to the object:
$oData = #()
Foreach($string in $strings)
{
$split = if($string.substring(0,4) -eq "This"){$String.Split(" ")}
$oData += [pscustomobject]#{
Part1 = if($Split){$split[0]}
Part2 = if($Split){$split[1]}
}
}
ISE underlines "Part2" with the message Unexpected token 'Part2' in expression or statement and the last curly brace of "Part1" has a an underline with the message The hash literal was incomplete.
When I run the script, the error is:
At line:13 char:38
+ Part1 = if($Split){$split[0]}
+ ~
The hash literal was incomplete.
At line:14 char:9
+ Part2 = if($Split){$split[1]}
+ ~~~~~
Unexpected token 'Part2' in expression or statement.
At line:16 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : IncompleteHashLiteral
To me, it seems a valid way to handle the null array and I'm sure I've used if statements in PSCustomObject values before.
I can work around the problem as I've done in the past when I've come across this problem before but I was wondering if anyone can shed light on why PowerShell doesn't like it.
I'm not entirely certain of the reason, but if you end your lines (all but the last so just the first one here) with semicolon ; it works fine. You can of course end all of them in ; for consistency as well.
I speculate it has something to do with the way the parser handles these, and it just doesn't know that the expression has ended for sure, whether it should know or not.
$oData += [pscustomobject]#{
Part1 = if($Split){$split[0]};
Part2 = if($Split){$split[1]}
}
The easy answer seems to be to add the else. As in:
$oData = #()
Foreach($string in $strings)
{
$split = if($string.substring(0,4) -eq "This"){$String.Split(" ")}
$oData += [pscustomobject]#{
Part1 = if($Split){$split[0]}else{$null}
Part2 = if($Split){$split[1]}else{$null}
Part3 = if($Split){$split[2]}else{$null}
Part4 = if($Split){$split[3]}else{$null}
Part5 = if($Split){$split[4]}else{$null}
}
}
Strange that PS is happy with:
if($something -eq $true)
{
"Hello"
}
Without the else or elseif part outside of the [pscustomobject].
Related
Context: Azure, Windows Server 2012, PowerShell 5
I've got the following code to convert all control characters (ascii and unicode whitespace other than \x20 itself) to their ampersand-hash equivalents.
function ConvertTo-AmpersandHash {
param ([Parameter(Mandatory)][String]$Value)
# there's got to be a better way of doing this.
$AMPERHASH = '&#'
$SEMICOLON = ';'
for ($i = 0x0; $i -lt 0x20; $i++) {
$value = $value -replace [char]$i,($AMPERHASH + $i + $SEMICOLON)
}
for ($i = 0x7f; $i -le 0xa0; $i++) {
$value = $value -replace [char]$i,($AMPERHASH + $i + $SEMICOLON)
}
return $Value
}
As can be seen by the embedded comment, I'm sure there's a better way to do this. As it stands, one does some 65 iterations for each incoming string. Would regular expressions work better/faster?
LATER
-replace '([\x00-\x1f\x7f-\xa0])',('&#' + [byte][char]$1 + ';')
looks promising but the $1 is evaluating to zero all the time, giving me all the time.
LATER STILL
Thinking that -replace couldn't internally iterate, I came up with
$t = [char]0 + [char]1 + [char]2 + [char]3 + [char]4 + [char]5 + [char]6
$r = '([\x00-\x1f\x7f-\xa0])'
while ($t -match [regex]$r) {
$t = $t -replace [regex]$r, ('&#' + [byte][char]$1 + ';')
}
echo $t
However out of that I still get
FINALLY
function ConvertTo-AmpersandHash {
param ([Parameter(Mandatory)][String]$Value)
$AMPERHASH = '&#'
$SEMICOLON = ';'
$patt = '([\x00-\x1f\x7f-\xa0]{1})'
while ($Value -match [regex]$patt) {
$Value = $Value -replace $Matches[0], ($AMPERHASH + [byte][char]$Matches[0] + $SEMICOLON)
}
return $Value
}
That works better. Faster too. Any advances on that?
Kory Gill's answer with the library call is surely a better approach, but to address your regex question, you can't evaluate code in the replacement with the -replace operator.
To do that, you need to use the .Net regex replace method, and pass it a scriptblock to evaluate the replacement, which takes a parameter of the match. e.g.
PS C:\> [regex]::Replace([string][char]2,
'([\x00-\x20\x7f-\xa0])',
{param([string]$m) '&#' + [byte][char]$m + ';'})
Your question is a little unclear to me, and could be a duplicate of What is the best way to escape HTML-specific characters in a string (PowerShell)?.
It would be nice if you explicitly stated the exact string you have and what you want it to converted to. One has to read the code to try to guess.
I am guessing one or more of these functions will do what you want:
$a = "http://foo.org/bar?baz & also <value> conversion"
"a"
$a
$b = [uri]::EscapeDataString($a)
"b"
$b
$c = [uri]::UnescapeDataString($b)
"c"
$c
Add-Type -AssemblyName System.Web
$d = [System.Web.HttpUtility]::HtmlEncode($a)
"d"
$d
$e = [System.Web.HttpUtility]::HtmlDecode($d)
"e"
$e
Gives:
a
http://foo.org/bar?baz & also <value> conversion
b
http%3A%2F%2Ffoo.org%2Fbar%3Fbaz%20%26%20also%20%3Cvalue%3E%20conversion
c
http://foo.org/bar?baz & also <value> conversion
d
http://foo.org/bar?baz & also <value> conversion
e
http://foo.org/bar?baz & also <value> conversion
I have one small function which helps me replacing as per my requirement:
$SpecChars are all the characters that are going to be replaced with nothing
Function Convert-ToFriendlyName
{param ($Text)
# Unwanted characters (includes spaces and '-') converted to a regex:
$SpecChars = '\', ' ','\\'
$remspecchars = [string]::join('|', ($SpecChars | % {[regex]::escape($_)}))
# Convert the text given to correct naming format (Uppercase)
$name = (Get-Culture).textinfo.totitlecase(“$Text”.tolower())
# Remove unwanted characters
$name = $name -replace $remspecchars, ""
$name
}
Example: Convert-ToFriendlyName "My\Name\isRana\Dip " will result me "MyNameIsranaDip".
Hope it helps you.
I want to be able to scan for two separate values in a line how do I do this?
$file = Get-Content "D:\path\tmp\certlist.txt"
foreach ($line in $file)
{
if ($line.StartsWith("snl") or $line.StartsWith("HSM1"))
{
$baseKey = "app.swift.snl."
$profileName = $line.Substring(0,13).TrimEnd()
$certType = $line.Substring(29,10).TrimEnd()
$renewalDate = Get_Unixtime $line.Substring(42,11).TrimEnd()
$expiryDate = Get_Unixtime $line.Substring(58,11).TrimEnd()
Send_Zabbix ($baseKey + $profileName + "." + $certType + ".renewaldate") $renewalDate
Send_Zabbix ($baseKey + $profileName + "." + $certType + ".expirydate") $expiryDate
}
}
The above doesn't work. It gives me an unexpected token.
For checking if a variable starts with one of several values a regular expression might be a better approach than daisy-chained StartsWith() calls.
if ($line -cmatch '^(snl|HSM1)')
{
...
}
^ matches the beginning of a string, (snl|HSM1) matches either snl or HSM1. -cmatch does a case-sensitive match. If you don't need case-sensitivity use -imatch or just -match.
Apparently all I needed was a '-'
if ($line.StartsWith("snl") -or $line.StartsWith("HSM1"))
I am trying to follow this example in order to attach images to an email with powershell. Here is the part of the code that behaves strange:
if ($DirectoryInfo) {
foreach ($element in $DirectoryInfo) {
$failedTest = $element| Select-Object -Expand name
$failedTests += $failedTest
$failedTestLog = "$PathLog\$failedTest.log"
$logContent = [IO.File]::ReadAllText($failedTestLog)
$imageDir = "$PathLog\$element\Firefox\*"
$imageSearch = Get-ChildItem -Path $imageDir -Include *.png -Recurse -Force
$imageFullname = $imageSearch | select FullName | Select-Object -Expand Fullname
$imageFilename = $imageSearch | Select-Object -Expand name
$imageFilename
$imageFullname
# *** THE FOLLOWING LINE CAUSES THE ERROR ***
$attachment = New-Object System.Net.Mail.Attachment –ArgumentList $imageFullname.ToString() # *** CAUSING ERROR ***
#$attachment.ContentDisposition.Inline = $True
#$attachment.ContentDisposition.DispositionType = "Inline"
#$attachment.ContentType.MediaType = "image/jpg"
#$attachment.ContentId = '$imageFilename'
#$msg.Attachments.Add($attachment)
$outputLog += "
********************************************
$failedTest
********************************************
$logContent
"
}
} else {
$outputLog = '** No failed tests **'
}
# Create the Overview report
$outputSummary = ""
foreach ($element in $scenarioInfo) {
if (CheckTest $failedTests $element) {
$outputSummary += "
$element : FAILED" # *** ERROR LINE ***
} Else {
$outputSummary += "
$element : Passed"
}
}
If I comment out the line which defines the attachment, the code works fine. If I use the code as it is, I get the following error:
Unexpected token ':' in expression or statement.
At D:\Testing\Data\Powershell\LoadRunner\LRmain.ps1:112 char:11
+ $element : <<<< FAILED"
+ CategoryInfo : ParserError: (::String) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
which refers to the line at the bottom of the script where it says "ERROR LINE". What the heck is going on? The behavior look completely illogical to me! I don't understand how a statement, which has no effect at all, can cause an error elsewhere! What is the problem and how to fix it...?
Also it does not matter if I use $imageFullname or $imageFullname.ToString() in the offending line.
Try to replace "$element : FAILED" by
"$element` : FAILED"
The reverse quote will escape the semicolon; which has a specific meaning in PowerShell. (It allows to output subproperty : $env:username for example)
Define the $outputSummary as Array:
$outputSummary = #()
instead of
$outputSummary = ""
I've got an array definition:
> $a={"abc","xyz","hello"}
Then, using foreach to modify it, but seems original elements are not changed:
> foreach($i in $a){$i="kkk"+$i}
> $a
> "abc","xyz","hello"
Why foreach loop doesn't modify elements?
Then I tried to use ForEach-Object, this time, it doesn't even run:
> $a|%{$_=$_+"kkk"}
Method invocation failed because [System.Management.Automation.ScriptBlock] does not contain a method named 'op_Addition'.
At line:1 char:6
+ $a|%{$_=$_+"kkk"}
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Does this has any syntax error? Or my understanding is incorrect?
You don't have an array in $a, you have a [ScriptBlock]. Curly braces {} denote a script block in PowerShell.
The array operator is #().
This is the cause of the error in the second example.
$a=#("abc","xyz","hello")
foreach($i in $a) {
$i = "zzz" + $i
}
$a | % { $_ += "zzz" }
However, this still will not work, because $i and $_ are copies, not references back to the original array location.
Instead, you can iterate over the array using a regular for loop:
for( $i = 0 ; $i -lt $a.Count ; $i++ ) {
$a[$i] += "zzz"
}
In this example you can see that in the loop body, you are referring directly to the array in $a and modifying its actual value.
Also note that ForEach-Object (%) returns a value (or all of the values returned from the block), so you could also do this:
$a = $a | % { "qqq" + $_ }
However this is forming a brand new array and assigning it to $a, not really modifying the original.
I am currently working on a project in PowerShell. The project downloads the NVD database XML, loops through a separate CSV for scan results from Nexpose, and pulls CVSS scores for each vulnerability identified with a CVE number.
It seems that matching the CVE number from the sheet (string) with the CVE number in the XML (also a string) is failing completely. The code i am using is below:
clear
[xml]$nvdxml = (New-Object system.Net.WebClient).DownloadString("http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-recent.xml")
$nsmgr = New-Object System.XML.XmlNamespaceManager($nvdxml.NameTable)
$nsmgr.AddNamespace('xsi','http://www.w3.org/2001/XMLSchema-instance')
$nsmgr.AddNamespace('vuln','http://scap.nist.gov/schema/vulnerability/0.4')
$nsmgr.AddNamespace('cvss','http://static.nvd.nist.gov/feeds/xml/cve/nvdcve-2.0-recent.xml')
$nsmgr.AddNamespace('df','http://scap.nist.gov/schema/feed/vulnerability/2.0')
$nvdxml.SelectNodes('//vuln:product',$nsmgr) | out-null
$nvdxml.SelectNodes('//vuln:vulnerable-configuration',$nsmgr) | out-null
$nvdxml.SelectNodes('//vuln:vulnerable-software-list',$nsmgr) | out-null
$nvdxml.SelectNodes('//default:nvd',$nsmgr) | out-null
$nvdxml.SelectNodes('//default:entry',$nsmgr) | out-null
$x = import-csv "test-report.csv"
$items = #()
$x | where {$_."Vulnerability Test Result Code" -like "v*"} | %{
$item = #{}
$vid = $_."Vulnerability CVE IDs"
$entry = ""
$item["Vname"] = $_."Vulnerability Title"
$item["VNode"] = $_."Asset IP Address"
$item['VID'] = $vid
$entry = $nvdxml.nvd.entry | where { $_."cve-id" -eq $vid }
$item['Score'] = $entry.cvss.base_metrics.score
$items += $item
}
$items
The $items array contains a vulnerability which has a CVE ID, but the string comparison is utterly failing. When I query for the object's type I get
You cannot call a method on a null-valued expression.
At line:25 char:19
+ $entry.GetType <<<< ()
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
When I assign a CVE ID to a string, and attempt to get the relevant vulnerability from the XML for that string, the comparison returns no results; yet, when I replace the variable with the quoted string of the same ID, the query returns the correct result. So, this would fail
$cveID = "CVE-2003-1567"
$nvdxml.nvd.entry | where { $_."cve-id" -eq $cveID }
However, this works fine
$nvdxml.nvd.entry | where { $_."cve-id" -eq "CVE-2003-1567" }
Any ideas? I have tried explicitly casting both $_."cve-id" and $cveID as String with the same results.
I would put all the entries in a hashtable, then look it up via the CVE ID:
$entriesByID = #{ }
$nvdxml.nvd.entry |
ForEach-Object { $entriesByID[$_.id] = $_ }
Note that instead of using the cve-id element, I'm using the id attribute on the event element.
Then, you can look it each entry in the hashtable:
$entry = $entriesByID[$vid]
If you're married to your original approach, you may be running into namespace issues. I would try using SelectNodes instead of PowerShell's virtual XML object properties:
$entry = $nvdxml.SelectSingleNode('/nvd/entry[vuln:cve-id = "$vid"]')
# or
$entry = $nvdxml.SelectSingleNode('/nvd/entry[#id = "$vid"]')