PowerShell String Array Pattern Matching - powershell

I have a PowerShell query.
I have the below 2 string arrays. What I want to be able to do is to return only those entries from $RecycleBinPaths that do not begin with all the entries from $ExcludeRecycleBinsPathWildCards (i.e. Wildcard pattern match)
$RecycleBinPaths = #('C:\$Recycle.Bin','D:\$RECYCLE.BIN','E:\$RECYCLE.BIN','F:\$RECYCLE.BIN','L:\$RECYCLE.BIN','M:\$RECYCLE.BIN','S:\$RECYCLE.BIN','T:\$RECYCLE.BIN')
$ExcludeRecycleBinsPathWildCards = #('C:\','F:\')
In the above example I want the output to be:
D:\$RECYCLE.BIN
E:\$RECYCLE.BIN
L:\$RECYCLE.BIN
M:\$RECYCLE.BIN
S:\$RECYCLE.BIN
T:\$RECYCLE.BIN
In fact $ExcludeRecycleBinsPathWildCards array above can contain any number of entries, not necessarily just 2 as shown above.
Are you able to help? Thanks.

Use nested .Where() methods:
$RecycleBinPaths.Where({
# Save path to separate variable
$path = $_
# Now test if _any_ string in the list of wildcards match, and then apply -not - making it resolve to true only if _none_ of the wildcards match the path
-not $ExcludeRecycleBinsPathWildCards.Where({ $path -like "$_*" }, 'First')
})

Another way of looping through the items and adding to another array.
$RecycleBinPaths = #('C:\$Recycle.Bin','D:\$RECYCLE.BIN','E:\$RECYCLE.BIN','F:\$RECYCLE.BIN','L:\$RECYCLE.BIN','M:\$RECYCLE.BIN','S:\$RECYCLE.BIN','T:\$RECYCLE.BIN')
$ExcludeRecycleBinsPathWildCards = #('C:\','F:\')
$SelectedRecycleBinPaths = #()
foreach($rbp in $RecycleBinPaths)
{
if(-Not ($ExcludeRecycleBinsPathWildCards -contains $rbp.ToString().Substring(0,3)))
{
$SelectedRecycleBinPaths += $rbp
}
}
$SelectedRecycleBinPaths
D:\$RECYCLE.BIN
E:\$RECYCLE.BIN
L:\$RECYCLE.BIN
M:\$RECYCLE.BIN
S:\$RECYCLE.BIN
T:\$RECYCLE.BIN

Related

Powershell - passing variable into loop for string with quotes

I'm trying to understand how to setup my script so that single quotes will wrap around my variable. I have a list of 1500 customers I need to repeat my script for, so my thought as to do a foreach loop.
$customerlist = Invoke-Sqlcmd -Query "SELECT [CustomerNo] FROM [TABLE]" -ServerInstance "SERVER\INSTANCE"
#Loop through
foreach ($customer in $customerlist)
{
$inputParams = #{
"CustomerNo" = "'"+$customer+"'";
}
....Do rest of script
}
I need the $customer variable in my $inputparams to show with the string value in single quotes, e.g. '01233456' instead of just 0123456. I've tried several different iterations of "'"+$customer+"'" but cannot seem to get the correct syntax. Could someone help me out?
Invoke-Sqlcmd returns [System.Data.DataRow] type objects with the field name(s) and value(s) of your query result as its properties (basically a table). To select the value in the CustomerNo, you must specify the value by name:
# Expand the CustomerNo property to strings
foreach ($customer in $customerlist.CustomerNo) { }
# Or, later in the script:
$inputParams = #{
CustomerNo = "'$($customer.CustomerNo)'"
}

I am trying to convert the "Return First recurring character in a string problem" from python to powershell

I have completed coding this same problem in python and was trying to generate a similar logic or atleast achieve a similar result in powershell.
Python Code-
def FRC(str):
h = {}
for ch in str:
if ch in h:
return ch
else:
h[ch] = 0
return '\0'
print(FRC("abcdedcba"))
I have tried a few possible codes and was able to only enumerate the array of characters to count their occurrences. Thank you for any suggestions.
Update1 - The code I have worked on is as follows:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #()
$teststring = $teststring.ToCharArray()
foreach ($letter in $teststring)
{
if($letter -contains $hash){
return $letter
}else {
$hash = $hash + $letter
}
return "\0"
}
}
get-duplicatechar("saahsahh")
You could use the (.Net) HashSet class for this, which Add method (besides adding the value,) returns true if the element is added to the HashSet<T> object and false if the element is already present.
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$str
)
$h = [System.Collections.Generic.HashSet[char]]::new()
foreach ($ch in $str.ToCharArray()) {
if(!$h.add($ch)) { return $ch }
}
}
Here's a working version using your code as base:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #{}
$CharArray = $teststring.ToCharArray()
foreach ($letter in $CharArray) {
if($letter -in $hash.Keys) {
$letter
break
}
else {
$hash[$letter] = $null
}
}
}
One problem is that you are strongly typing $teststring to be a string, so when you add a character array later PowerShell just converts it into a string and thus $teststring remains a string (try $teststring.GetType() after $teststring = $teststring.ToCharArray() to see this for yourself).
One way to solve this is to do what I did and use a different variable for the character array. You could also solve it by changing the variable to a character array directly by replacing [string]$teststring with [char[]]$teststring, that way any strings input to the function will be automatically cast as a character array.
The next mistake is using -contains where you need -in. The letter doesn't contain the array, you're looking for the letter in the array, just like you did in Python.
You can drop the return keyword entirely, PowerShell does not need it. Any output in your function will be output from the function automatically.
You also call your collection variable "hash", but you made an array. I changed it to be an actual hashtable, just like in your Python code. I also changed the way we add to it to more closely reflect what you did in Python. There are many ways to do this, this is just one. Notice we'll need to add ".Keys" in our if-statement as well so we check for keys matching our letter.
I think that's it, ask if anything is unclear.

Iterate through CSV and create an array

I am newbie to Powershell. Need a logic for CSV automation. I have a CSV log file contains large number of API calls.
I need to go row by row and segregate the data, output should be like below. Sum of calls count and average of response time to be updated.
I have written complicated If else conditions for different types of API calls and able to take the scenario name and other values from the csv. My pain starts here, struggling to come to conclusion to move forward. Can i create an array and store all the values then do all the calculation later or write the values in another csv then do all the calculation to find the Count and average response time?
If i choose array, scenario should not be duplicated. For me its really hard to take a decision without knowing the available cmdlets for array and CSV. Please throw some light..
Thanks in advance...
Here is an approach you can use a combination of c# available to Powershell (which can be MUCH more efficient handling larger files and data).
The first component is you need some consistent logic to isolate the API category you want each URL to be assigned. From your screenshots, sometimes it seems you use last segment of the URL but others it is some path in the middle of the resource.
Here is just a quick approach where you pass in an array of categories, and if it can be matched to URI in any way, then that category is used. Otherwise, the URI stands as its own category. Please replace with whatever logic you want here.
function Get-ApiCategory {
param([string[]] $Categories, [string] $Text)
foreach ($c in $Categories) {
if ($Text.IndexOf($c) -gt 0) {
return $c
}
}
return $Text # Not found
}
Then, here is a method that (1) reads the large CSV file row-by-row and uses basic parsing logic (since your source data seems simple enough) without loading the full file into memory, and then (2) exports a CSV file with summary data.
function Write-SummaryToFile {
param([string[]] $Categories, [string] $InputFile, [string] $Output)
# Parse the file line-by-line (optimize for memory)
$result = #{}
$lineNum = 0
Write-Host $InputFile
foreach ($line in [System.IO.File]::ReadLines($InputFile)) {
if ($lineNum++ -lt 1) { continue } # Skip header
$cols = $line.Split(',')
$category = Get-ApiCategory $Categories $cols[0]
$new = #{
Category = $category
Count = [int]$cols[1]
AvgResponse = [double]$cols[2]
}
if ($result.ContainsKey($category)) {
$weighted = $result[$category].AvgResponse * $result[$category].Count
$result[$category].Count += $new.Count
$result[$category].AvgResponse = ($weighted + $new.AvgResponse * $new.Count) / $result[$category].Count;
} else {
$result[$category] = $new
}
}
# Output to file
if (Test-Path $Output) { Remove-Item $Output }
try {
$stream = [System.IO.StreamWriter] $Output
$stream.WriteLine('Scenario,Count,Avg_Response_Time')
$result.Values | ForEach-Object { $stream.WriteLine([string]::Format("{0},{1},{2}", $_.Category, $_.Count, $_.AvgResponse.ToString("0.##"))) }
}
finally {
$stream.Dispose()
}
}
Then, you are able to call these methods in an example like this:
$categories = #('MoveRequestQueue', 'DeliveryDate')
Write-SummaryToFile $categories 'c:\dev\scratch\ps1\test.csv' 'C:\dev\scratch\ps1\Output.csv'

Powershell: Turn period delimited string into object properties

I have a string that looks something like this:
$string = "property1.property2.property3"
And I have an object, we'll call $object. If I try to do $object.$string it doesn't interpret it that I want property3 of property2 of property1 of $object, it thinks I want $object."property1.property2.property3".
Obviously, using split('.') is where I need to be looking, but I don't know how to do it if I have an unknown amount of properties. I can't statically do:
$split = $string.split('.')
$object.$split[0].$split[1].$split[2]
That doesn't work because I don't know how many properties are going to be in the string. So how do I stitch it together off of n amounts of properties in the string?
A simple cheater way to do this would be to use Invoke-Expression. It will build the string and execute it in the same way as if you typed it yourself.
$string = "property1.property2.property3"
Invoke-Expression "`$object.$string"
You need to escape the first $ since we don't want that expanded at the same time as $string. Typical warning: Beware of malicious code execution when using Invoke-Expression since it can do anything you want it to.
In order to avoid this you would have to build a recursive function that would take the current position in the object and pass it the next breadcrumb.
Function Get-NestedObject{
param(
# The object we are going to return a propery from
$object,
# The property we are going to return
$property,
# The root object we are starting from.
$rootObject
)
# If the object passed is null then it means we are on the first pass so
# return the $property of the $rootObject.
if($object){
return $object.$property
} else {
return $rootObject.$property
}
}
# The property breadcrumbs
$string = '"Directory Mappings"."SSRS Reports"'
# sp
$delimetedString = $String.Split(".")
$nestedObject = $null
Foreach($breadCrumb in $delimetedString){
$nestedObject = Get-NestedObject $nestedObject $breadcrumb $settings
}
$nestedObject
There are some obvious places where that function could be hardened and documented better but that should give you an idea of what you could do.
What's the use case here? You can split the string as you've described. This will create an array, and you can count the number of elements in the array so that n is known.
$string = "property1.property2.property3"
$split = $string.split('.')
foreach($i in 0..($split.Count -1)){
Write-Host "Element $i is equal to $($split[$i])"
$myString += $split[$i]
}

powershell "if" needs to set two variables on match

I need to set two variables in a script...
The first is obviously $vmcluster - What's the proper syntax to set another variable if my "if" matches?
if ($vmname -like "LouPr*") {$vmcluster = "Production"}
It's a script-block.... just use another line, or seperate with ; .
if ($vmname -like "LouPr*") {
$vmcluster = "Production"
$secondvar = "secondvalue"
}
or
if ($vmname -like "LouPr*") { $vmcluster = "Production"; $secondvar = "secondvalue" }