If list contains specific characters, assign variable - if-statement

I have this syntax that sets the Roaming aggressiveness to lowest. It works great provided every computer wifi manufacturer indexes their options from 1 through 5 and not descending order.
$RoamValue = (Get-NetAdapterAdvancedProperty "*Wi-Fi" -DisplayName *Roam*).ValidDisplayValues Set-NetadapterAdvancedProperty -Name "*Wi-Fi" -DisplayName "Roaming aggressiveness" -DisplayValue $RoamValue[0]"
The problem is if I run
Get-NetAdapterAdvancedProperty Wi-Fi -DisplayName *Roam* | ft ValidDisplayValues
The results on my computer is:
{1. Lowest, 2. Medium-low, 3. Medium, 4. Medium-High...}
I just discovered today on someone else's computer, his ValidDisplayValues are reversed (and with slightly different syntax):
{5.Highest, 4.Medium-high, 3.Medium, 2.Medium-low, 1.Lowest}
Another computer had:
{1 - Lowest, 2 - Medium-low, 3 - Medium, 4 - Medium-High...}
The point is my original script will now no longer work for all the computers in my environment. I'm fairly certain I need to do an If - Else statement to compare the values, look for the word containing "*Lowest" in the list regardless of the syntax before it, and if it sees it, assign that complete value to a variable so in the final statement, I can still just reference that variable to apply to ALL the computers in the environment regardless of how the manufacturer chooses to order them ascending or descending and regardless of whether they choose to use "1. Lowest" or "1.Lowest" or "1 - Lowest" or whatever other variation appears. The word "Lowest" seems to be a commonality they all have at least.
Please help with my bad if else statement.
Not very clean nor concise and I'm missing closing } statements but I have the concept of what I want to accomplish.
$RoamValue = (Get-NetAdapterAdvancedProperty "*Wi-Fi" -DisplayName *Roam*).ValidDisplayValues
If ($RoamValue[0] -contains "Lowest") {
$LowestRoam = $RoamValue[0]
}
elseif
{
if ($RoamValue[1] -contains "Lowest") {
$LowestRoam = $RoamValue[1]
}
elseif
{
if ($RoamValue[2] -contains "Lowest") {
$LowestRoam = $RoamValue[2]
}
elseif
{
if ($RoamValue[3] -contains "Lowest") {
$LowestRoam = $RoamValue[3]
}
elseif
{
if ($RoamValue[4] -contains "Lowest") {
$LowestRoam = $RoamValue[4]
}
else
{
$LowestRoam = $RoamValue[5]
}
$LowestRoam
Set-NetadapterAdvancedProperty -Name "*Wi-Fi" -DisplayName "Roaming aggressiveness" -DisplayValue $LowestRoam

Related

GSTIN Validation through condition not working in Perl

I have written Perl code for validating GSTIN Number which is related to India’s tax according to the following rules:
The first two digits represent the state code as per Indian Census 2011. Every state has a unique code.
The next ten digits will be the PAN number of the taxpayer
The thirteenth digit will be assigned based on the number of registration within a state
The fourteenth digit will be Z by default
The last digit will be for check code. It may be an alphabet or a number.
Following is the code:
my $gst_number_input = '35AABCS1429B1AX';
my $gst_number_character_count = length($gst_number_input);
my $gst_validation =~ /\d{2}[A-Z]{5}\d{4}[A-Z]{1}[A-Z\d]{1}[Z]{1}[A-Z\d]{1}/;
if ($gst_number_character_count == 15 && $gst_number_input =~ $gst_validation) {
print "GST Number is valid";
} else {
print "Invalid GST Number";
}
I have an invalid GSTIN input entered in the code. So when I run the script, I get:
GST Number is valid
Instead I should get the error because the GSTIN input is invalid:
Invalid GST Number
Can anyone please help ?
Thanks in advance
In this part you are using =~ where is should be an equals sign =
my $gst_validation =~ /\d{2}[A-Z]{5}\d{4}[A-Z]{1}[A-Z\d]{1}[Z]{1}[A-Z\d]{1}/;
If you want to use is as a variable, you could use qr
Note that you can omit {1} from the pattern and you don't have to use the square brackets around [Z]
You code might look like
my $gst_number_input = '35AABCS1429B1AX';
my $gst_number_character_count = length($gst_number_input);
my $gst_validation = qr/\d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d]Z[A-Z\d]/;
if ($gst_number_character_count == 15 && $gst_number_input =~ $gst_validation) {
print "GST Number is valid";
} else {
print "Invalid GST Number";
}

Merge/combine two lists line by line?

I have two lists stored in variables: $list1 and $list2, for example:
$list1:
a
b
c
d
$list2:
1
2
3
4
How do I merge them together line by line so that I end up with:
a1
b2
c3
d4
I have tried using array (#) but it just combines them one after the other, not line by line, example:
$list1 = #(command)
$list1 += #($list2)
If you prefer pipelining, you can also do it in one line:
0 .. ($list1.count -1) | ForEach-Object { $list1[$_]+$list2[$_] }
You could do this with a For loop that uses iterates through the index of each object until it reaches the total (.count) of the first object:
$list1 = 'a','b','c','d'
$list2 = 1,2,3,4
For ($i=0; $i -lt $list1.count; $i++) {
$list1[$i]+$list2[$i]
}
Output:
a1
b2
c3
d4
If you want the results to go to a variable, you could put (for example) $list = before the For.
To complement Mark Wragg's helpful for-based answer and Martin Brandl's helpful pipeline-based answer:
Combining foreach with .., the range operator allows for a concise solution that also performs well:
foreach ($i in 0..($list1.count-1)) { "$($list1[$i])$($list2[$i])" }
Even though an entire array of indices is constructed first - 0..($list1.count-1) - this slightly outperforms the for solution with large input lists, and both foreach and for will be noticeably faster than the pipeline-based solution - see below.
Also note how string interpolation (variable references and subexpressions inside a single "..." string) are used to ensure that the result is always a string.
By contrast, if you use +, it is the type of the LHS that determines the output type, which can result in errors or unwanted output; e.g., 1 + 'a' causes an error, because 1 is an integer and 'a' cannot be converted to an integer.
Optional reading: performance considerations
Generally, foreach and for solutions are noticeably faster than pipeline-based (ForEach-Object cmdlet-based) solutions.
Pipelines are elegant and concise, but they are comparatively slow.
That shouldn't stop you from using them, but it's important to be aware that they can be a performance bottleneck.
Pipelines are memory-efficient, and for processing large collections that don't fit into memory as a whole they are always the right tool to use.
PSv4 introduced the little-known .ForEach() collection operator (method), whose performance is in between that of for / foreach and the ForEach-Object cmdlet.
The following compares the relative performance with large lists (100,000 items); the absolute timing numbers will vary based on many factors, but they should give you a general sense:
# Define two large lists.
$list1 = 1..100000
$list2 = 1..100000
# Define the commands as script blocks:
$cmds = { foreach ($i in 0..($list1.count-1)) { "$($list1[$i])$($list2[$i])" } },
{ for ($i=0; $i -lt $list1.count; $i++) { "$($list1[$i])$($list2[$i])" } },
{ 0..($list1.count -1) | ForEach-Object { "$($list1[$_])$($list2[$_])" } },
{ (0..($list1.count-1)).ForEach({ "$($list1[$_])$($list2[$_])" }) }
# Time each command.
$cmds | ForEach-Object { '{0:0.0}' -f (Measure-Command $_).TotalSeconds }
In a 2-core Windows 10 VM running PSv5.1 I get the following results after running the tests several times:
0.5 # foreach
0.7 # for
1.8 # ForEach-Object (pipeline)
1.2 # .ForEach() operator

Powershell: How to Validate Range between 1 to 15 and it should accepts pattern as 1,2,3,4,5 to 15

I am a newbie in Power Shell Scripting.
I am trying to Achieve a functionality, that should accepts inputs from user in below criteria
Only Digits
Range Between 1 to 15
Should accept String of Array with comma Separated values ex: 1,2,3,4,14,15
Can Contain space in between commas
Values should not be duplicated
The Returned values must be Array
Till now, I have tried
Function Validate-Choice{
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True)]
[ValidateRange(1,15)]
[string[]]$Item
)
Process {$Item}
}
Validate-Choice 1,2,3,4,5,6,7,8,9,10,11,13 # Similar Way i want O/p
Out Put:
1
2
3
4
5
6
7
8
9
10
11
13
$ReadInput = Read-Host -prompt "Please Choose from the list [1/2/3/4/5/6/7/8/9/10/11/12/13/14] You can select multiple Values EX: 1, 2, 3 -- "
$userchoices = Validate-Choice -item $ReadInput
$userchoices
If read the same input from Host Getting Below Error
Validate-Choice : Cannot validate argument on parameter 'Item'. The argument cannot be validated because
its type "String" is not the same type (Int32) as the maximum and minimum limits of the parameter. Make sure the argument is of type Int32 and then try the command again. At line:10 char:21
+ Validate-Choice '1,2,3,4,5,6,7,8,9,10,11,13'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Validate-Choice], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Validate-Choice
And also i am trying with different Regex patterns. But failing
Function Test-Something {
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory=$True)]
[ValidatePattern('(?:\s*\d{1,15}[1-15]\s*(?:,|$))+$')]
[string[]]$Item
)
Process { $Item }
}
The above functions are partially resulting.
Can any one please help me here..!?
This would probably be easiest if you just changed the parameter type to [int[]] then your ValidateRange attribute does most of the work. It doesn't handle duplicates though. Turns out you can't use [ValidateScript()] as #PetSerAl points out. So that leaves checking the parameter the old fashioned way, in a begin block:
Function Test-Something {
[cmdletbinding()]
Param(
[Parameter(Position=0, Mandatory)]
[ValidateRange(1, 15)]
[int[]]$Item
)
Begin {
$ht = #{}
foreach ($i in $Item) {
if ($ht.ContainsKey("$i")) {
throw "Parameter Item must contain unique values - duplicate '$i'"
}
else {
$ht["$i"] = $i
}
}
}
Process {
[string]$Item
}
}
Test-Something (1,2,3,4,3)
Note that this won't work if you make the Item parameter accept pipeline input. In the pipeline input case, $Item will not be set in the begin block.
Naren_Ch
Your 1st usage of the advanced function (AF)
Validate-Choice 1,2,3,4,5,6,7,8,9,10,11,13 # Similar Way i want O/p
is correct - you are inputting data as expected by the AF.
Now, look at the example reading the input from the host:
$ReadInput = Read-Host -prompt "Please Choose from the list [1/2/3/4/5/6/7/8/9/10/11/12/13/14] You can select multiple Values EX: 1, 2, 3 -- "
When you do this, $ReadInput is a string, and in this case, it's a string full of commas!
Consequently, your data inputted to the AF will result in error caused by validation code, written by yourself.
To correct the situation, just do this:
$ReadInput = (Read-Host -prompt "Please Choose etc...") -split ','
$userchoices = Validate-Choice -item $ReadInput
You must remember that data read by Read-Host is a string (just 1 string).

Find words, that are substrings of other words efficiently

I have an Ispell list of english words (nearly 50 000 words), my homework in Perl is to get quickly (like under one minute) list of all strings, that are substrings of some other word. I have tried solution with two foreach cycles comparing all words, but even with some optimalizations, its still too slow. I think, that right solution could be some clever use of regular expressions on array of words. Do you know how to solve this problem quicky (in Perl)?
I have found fast solution, which can find some all these substrings in about 15 seconds on my computer, using just one thread. Basically, for each word, I have created array of every possible substrings (eliminating substrings which differs only in "s" or "'s" endings):
#take word and return list of all valid substrings
sub split_to_all_valid_subwords {
my $word = $_[0];
my #split_list;
my ($i, $j);
for ($i = 0; $i < length($word); ++$i){
for ($j = 1; $j <= length($word) - $i; ++$j){
unless
(
($j == length($word)) or
($word =~ m/s$/ and $i == 0 and $j == length($word) - 1) or
($word =~ m/\'s$/ and $i == 0 and $j == length($word) - 2)
)
{
push(#split_list, substr($word, $i, $j));
}
}
}
return #split_list;
}
Then I just create list of all candidates for substrings and make intersection with words:
my #substring_candidates;
foreach my $word (#words) {
push( #substring_candidates, split_to_all_valid_subwords($word));
}
#make intersection between substring candidates and words
my %substring_candidates=map{$_ =>1} #substring_candidates;
my %words=map{$_=>1} #words;
my #substrings = grep( $substring_candidates{$_}, #words );
Now in substrings I have array of all words, that are substrings of some other words.
Perl regular expressions will optimize patterns like foo|bar|baz into an Aho-Corasick match - up to a certain limit of total compiled regex length. Your 50000 words will probably exceed that length, but could be broken into smaller groups. (Indeed, you probably want to break them up by length and only check words of length N for containing words of length 1 through N-1.)
Alternatively, you could just implement Aho-Corasick in your perl code - that's kind of fun to do.
update
Ondra supplied a beautiful solution in his answer; I leave my post here as an example of overthinking a problem and failed optimisation techniques.
My worst case kicks in for a word that doesn't match any other word in the input. In that case, it goes quadratic. The OPT_PRESORT was a try to advert the worst case for most words. The OPT_CONSECUTIVE was a linear-complexity filter that reduced the total number of items in the main part of the algorithm, but it is just a constant factor when considering the complexity. However, it is still useful with Ondras algorithm and saves a few seconds, as building his split list is more expensive than comparing two consecutive words.
I updated the code below to select ondras algorithm as a possible optimisation. Paired with zero threads and the presort optimisation, it yields maximum performance.
I would like to share a solution I coded. Given an input file, it outputs all those words that are a substring of any other word in the same input file. Therefore, it computes the opposite of ysth's ideas, but I took the idea of optimisation #2 from his answer. There are the following three main optimisations that can be deactivated if required.
Multithreading
The questions "Is word A in list L? Is word B in L?" can be easily parallelised.
Pre-sorting all the words for their length
I create an array that points to the list of all words that are longer than a certain length, for every possible length. For long words, this can cut down the number of possible words dramatically, but it trades quite a lot of space, as one word of length n appears in all lists from length 1 to length n.
Testing consecutive words
In my /usr/share/dict/words, most consecutive lines look quite similar:
Abby
Abby's
for example. As every word that would match the first word also matches the second one, I immediately add the first word to the list of matching words, and only keep the second word for further testing. This saved about 30% of words in my test cases. Because I do that before optimisation No 2, this also saves a lot of space. Another trade-off is that the output will not be sorted.
The script itself is ~120 lines long; I explain each sub before showing it.
head
This is just a standard script header for multithreading. Oh, and you need perl 5.10 or better to run this. The configuration constants define the optimisation behaviour. Add the number of processors of your machine in that field. The OPT_MAX variable can take the number of words you want to process, however this is evaluated after the optimisations have taken place, so the easy words will already have been caught by the OPT_CONSECUTIVE optimisation. Adding anything there will make the script seemingly slower. $|++ makes sure that the status updates are shown immediately. I exit after the main is executed.
#!/usr/bin/perl
use strict; use warnings; use feature qw(say); use threads;
$|=1;
use constant PROCESSORS => 0; # (false, n) number of threads
use constant OPT_MAX => 0; # (false, n) number of words to check
use constant OPT_PRESORT => 0; # (true / false) sorts words by length
use constant OPT_CONSECUTIVE => 1; # (true / false) prefilter data while loading
use constant OPT_ONDRA => 1; # select the awesome Ondra algorithm
use constant BLABBER_AT => 10; # (false, n) print progress at n percent
die q(The optimisations Ondra and Presort are mutually exclusive.)
if OPT_PRESORT and OPT_ONDRA;
exit main();
main
Encapsulates the main logic, and does multi-threading. The output of n words will be matched will be considerably smaller than the number of input words, if the input was sorted. After I have selected all matched words, I print them to STDOUT. All status updates etc. are printed to STDERR, so that they don't interfere with the output.
sub main {
my #matching; # the matching words.
my #words = load_words(\#matching); # the words to be searched
say STDERR 0+#words . " words to be matched";
my $prepared_words = prepare_words(#words);
# do the matching, possibly multithreading
if (PROCESSORS) {
my #threads =
map {threads->new(
\&test_range,
$prepared_words,
#words[$$_[0] .. $$_[1]] )
} divide(PROCESSORS, OPT_MAX || 0+#words);
push #matching, $_->join for #threads;
} else {
push #matching, test_range(
$prepared_words,
#words[0 .. (OPT_MAX || 0+#words)-1]);
}
say STDERR 0+#matching . " words matched";
say for #matching; # print out the matching words.
0;
}
load_words
This reads all the words from the input files which were supplied as command line arguments. Here the OPT_CONSECUTIVE optimisation takes place. The $last word is either put into the list of matching words, or into the list of words to be matched later. The -1 != index($a, $b) decides if the word $b is a substring of word $a.
sub load_words {
my $matching = shift;
my #words;
if (OPT_CONSECUTIVE) {
my $last;
while (<>) {
chomp;
if (defined $last) {
push #{-1 != index($_, $last) ? $matching : \#words}, $last;
}
$last = $_;
}
push #words, $last // ();
} else {
#words = map {chomp; $_} <>;
}
#words;
}
prepare_words
This "blows up" the input words, sorting them after their length into each slot, that has the words of larger or equal length. Therefore, slot 1 will contain all words. If this optimisation is deselected, it is a no-op and passes the input list right through.
sub prepare_words {
if (OPT_ONDRA) {
my $ondra_split = sub { # evil: using $_ as implicit argument
my #split_list;
for my $i (0 .. length $_) {
for my $j (1 .. length($_) - ($i || 1)) {
push #split_list, substr $_, $i, $j;
}
}
#split_list;
};
return +{map {$_ => 1} map &$ondra_split(), #_};
} elsif (OPT_PRESORT) {
my #prepared = ([]);
for my $w (#_) {
push #{$prepared[$_]}, $w for 1 .. length $w;
}
return \#prepared;
} else {
return [#_];
}
}
test
This tests if the word $w is a substring in any of the other words. $wbl points to the data structure that was created by the previous sub: Either a flat list of words, or the words sorted by length. The appropriate algorithm is then selected. Nearly all of the running time is spent in this loop. Using index is much faster than using a regex.
sub test {
my ($w, $wbl) = #_;
my $l = length $w;
if (OPT_PRESORT) {
for my $try (#{$$wbl[$l + 1]}) {
return 1 if -1 != index $try, $w;
}
} else {
for my $try (#$wbl) {
return 1 if $w ne $try and -1 != index $try, $w;
}
}
return 0;
}
divide
This just encapsulates an algorithm that guarantees a fair distribution of $items items into $parcels buckets. It outputs the bounds of a range of items.
sub divide {
my ($parcels, $items) = #_;
say STDERR "dividing $items items into $parcels parcels.";
my ($min_size, $rest) = (int($items / $parcels), $items % $parcels);
my #distributions =
map [
$_ * $min_size + ($_ < $rest ? $_ : $rest),
($_ + 1) * $min_size + ($_ < $rest ? $_ : $rest - 1)
], 0 .. $parcels - 1;
say STDERR "range division: #$_" for #distributions;
return #distributions;
}
test_range
This calls test for each word in the input list, and is the sub that is multithreaded. grep selects all those elements in the input list where the code (given as first argument) return true. It also regulary outputs a status message like thread 2 at 10% which makes waiting for completition much easier. This is a psychological optimisation ;-).
sub test_range {
my $wbl = shift;
if (BLABBER_AT) {
my $range = #_;
my $step = int($range / 100 * BLABBER_AT) || 1;
my $i = 0;
return
grep {
if (0 == ++$i % $step) {
printf STDERR "... thread %d at %2d%%\n",
threads->tid,
$i / $step * BLABBER_AT;
}
OPT_ONDRA ? $wbl->{$_} : test($_, $wbl)
} #_;
} else {
return grep {OPT_ONDRA ? $wbl->{$_} : test($_, $wbl)} #_;
}
}
invocation
Using bash, I invoked the script like
$ time (head -n 1000 /usr/share/dict/words | perl script.pl >/dev/null)
Where 1000 is the number of lines I wanted to input, dict/words was the word list I used, and /dev/null is the place I want to store the output list, in this case, throwing the output away. If the whole file should be read, it can be passed as an argument, like
$ perl script.pl input-file >output-file
time just tells us how long the script ran. Using 2 slow processors and 50000 words, it executed in just over two minutes in my case, which is actually quite good.
update: more like 6–7 seconds now, with the Ondra + Presort optimisation, and no threading.
further optimisations
update: overcome by better algorithm. This section is no longer completely valid.
The multithreading is awful. It allocates quite some memory and isn't exactly fast. This isn't suprising considering the amount of data. I considered using a Thread::Queue, but that thing is slow like $#*! and therefore is a complete no-go.
If the inner loop in test was coded in a lower-level language, some performance might be gained, as the index built-in wouldn't have to be called. If you can code C, take a look at the Inline::C module. If the whole script were coded in a lower language, array access would also be faster. A language like Java would also make the multithreading less painful (and less expensive).

Regex to extract Asterisk queue statistics

I'm trying to construct some regex to extract stats on queue statuses in Asterisk. I'm relatively new to regex so am quite far off a solution. I have the following output to parse:
Parsing /etc/asterisk/extconfig.conf
0009*007 has 2 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:7, SL:0.0% within 0s
Members:
0009*001 (Local/0009*001#queue/nj) (In use) has taken no calls yet
Callers:
1. SIP/chan5-000a29f2 (wait: 0:08, prio: 0)
2. SIP/0139*741-000a29f7 (wait: 0:03, prio: 0)
The real output will have info for multiple queues, so it will repeat from the second line. The first line is only displayed once.
I need to end up with the queue ID (in this example 0009*007) and a list of calls with their respective wait time.
So far I have used the following regex to match the queue number:
\b^[0-9]{4}\*[0-9]{3}\b
But this doesn't work. Not sure how to match the call with the wait time.
Ideally I would like output like this:
0009*007,1,0:08
0009*007,2,0:03
I will be writing the final script in Perl most likely.
Here's a simple state machine solution. The regexes may need to change depending on what kind of variation you expect in the log file.
use Modern::Perl;
my $current_queue;
my $in_callers = 0;
while (<DATA>)
{
if (!defined $current_queue)
{
/(\d{4}\*\d{3})/ and $current_queue = $1;
}
elsif (!$in_callers)
{
/Callers:/ and $in_callers++;
}
elsif (/^\s*(\d+)\..*wait:\s+(\d+:\d+),\s+prio:\s+(\d+)/)
{
say "$current_queue,$1,$2,$3";
}
else
{
#end of this queue; reset.
undef $current_queue; $in_callers = 0;
}
}
__DATA__
Parsing /etc/asterisk/extconfig.conf
0009*007 has 2 calls (max unlimited) in 'ringall' strategy
Members:
0009*001 (Local/0009*001#queue/nj) (In use) has taken no calls yet
Callers:
1. SIP/chan5-000a29f2 (wait: 0:08, prio: 0)
2. SIP/0139*741-000a29f7 (wait: 0:03, prio: 0)
As m.buettner indicated, you won't be able to do this with one regex. You can rely on your knowledge of the repeating nature of the data to generate a hash with the data you need, however, and then print the hash at the end:
#!/usr/bin/perl
my %queues;
my $current_queue;
while (<DATA>) {
chomp;
if (m/^(\d+\*\d+)/) {
$current_queue = $1;
}
elsif (m/^\s+(\d)\..+?\(wait:\s+([\d\:]+),/) {
$queues{$current_queue}{$1} = $2;
}
}
foreach my $queue (sort keys %queues) {
foreach my $caller (sort keys %{ $queues{$queue} }) {
print join (',', $queue, $caller, $queues{$queue}{$caller}) . "\n";
}
}
exit;
__DATA__
Parsing /etc/asterisk/extconfig.conf
0009*007 has 2 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:7, SL:0.0% within 0s
Members:
0009*001 (Local/0009*001#queue/nj) (In use) has taken no calls yet
Callers:
1. SIP/chan5-000a29f2 (wait: 0:08, prio: 0)
2. SIP/0139*741-000a29f7 (wait: 0:03, prio: 0)