Capture specific text from output into a variable - powershell

I'm trying to write a script that will detect what COM port a device is plugged into, then map it to a new port.
Here is the output from the "change port" command:
PS C:\> change port
COM11 = \Device\19H2KP0
COM2 = \Device\Serial1
COM5 = \Device\Serial2
KEYSPAN#*USA19HMAP#00_00#{4d36e978-e325-11ce-bfc1-08002be10318} = \Device\ComUs19H-00
KEYSPAN#*USA19HMAP#00_00#{86e0d1e0-8089-11d0-9ce4-08003e301f73} = \Device\ComUs19H-00
USA19HP0 = \Device\19H2KP0
I need to capture the COM number prior to "\Device\19H2KP0". So in this example output, I would capture COM11 into a variable.
Next I need to run the "change port" command with that variable. i.e.:
change port COM1=$CapturedText
Thank you in advance for any assistance!

Do you already know what the 19H2KP0 bit will be? If so, you could use a regular expression to detect the unique ID using a look-ahead.
Here's a fully working example, using your example text:
$Output = #"
COM11 = \Device\19H2KP0
COM2 = \Device\Serial1
COM5 = \Device\Serial2
KEYSPAN#*USA19HMAP#00_00#{4d36e978-e325-11ce-bfc1-08002be10318} = \Device\ComUs19H-00
KEYSPAN#*USA19HMAP#00_00#{86e0d1e0-8089-11d0-9ce4-08003e301f73} = \Device\ComUs19H-00
USA19HP0 = \Device\19H2KP0
"#
$Output -match "COM[0-9]{1,2}(?=.*$Id)";
Write-Host -Object ('COM port is: {0}' -f $matches[0]);
And now here is the example, using the in-line command:
$Id = '19H2KP0';
$Output = change port;
$Output -match "COM[0-9]{1,2}(?=.*$Id)";
Write-Host -Object ('COM port is: {0}' -f $matches[0]);
Result
COM port is: COM11

Longer, but perhaps more intuitive, you can also use chained -match and -replace operators with simpler regexes:
$CapturedText = (change port) -match 'COM.+19h2kp0' -replace '^(COM\d+).+','$1'
$CapturedText

Related

Trying to put together a script for automatic timezone updates

I am working on a powershell script and it isn't reading my "If, elseIf" properly so I know I am doing something wrong. I need some help figuring this out.
I start with pulling the Default Gateway and storing it:
$Gateway = (Get-wmiObject Win32_networkAdapterConfiguration | ?{$_.IPEnabled}).DefaultIPGateway
Next I am trying to get it to sort that so that if it equals one of my designated Default Gateways it will update the timezone.
If ($Gateway = "10.100.4.1")
{
$TZ = "Central Standard Time"
}
ElseIf ($Gateway = "10.101.4.1")
{
$TZ = "Central Standard Time"
}
and I am finishing it up with a
Set-TimeZone $TZ
The purpose is that if I image a system at the home office, and I ship it to "remote location" I can't trust an end user to update their time zone, and I have POS that was poorly written so that it doesn't use UTC/GMT, and can cause issues with the BOH systems et cetera.
I will be placing this in as a startup item to execute whenever the system starts up to ensure that it is always up to date with the TZ.
Changing the Win 10 to use an automatic update for the TZ doesn't work because reasons (read: Networking Team and Security Team are out to get me and in this instance it isn't paranoia).
So, where can I find help to put this all together?
Edit: I had a typo which is why it wasn't working. So... nevermind. For those ofyou interested in the typo, I removed it already. it was in my $Gateway portion, adding a " after {$_.IPEnabled} and before the )
Your "if" statements are using the assignment operator = instead of the equality operator, -eq.
Switch it to If ($Gateway -eq "10.100.4.1") and it should work.
P.S. I missed the typo you mentioned, but the assignment operator is still an issue. When the assignment operator is used in an "if/elsif" statment it will always return $true which would be rather problematic.
I would suggest using a lookup Hashtable for the designated default gateways.
Because you want this to be run as startup script, you also need to create a centralized path where errors can be logged or write errors in the windows eventlog.
Create a Hashtable with your designated gateway IP addresses as key and the timezone ids as value.
Either directly in code:
$timeZones = #{
'10.100.4.1' = 'Central Standard Time'
'10.101.4.1' = 'Central Standard Time'
'10.102.4.1' = 'Eastern Standard Time'
# etc.
}
Or by reading a centralized CSV file with columns 'IPAddress' and 'TimeZone'
$defaultTz = Import-Csv -LiteralPath '\\Server\Share\Folder\Timezones.csv'
$timeZones = #{}
$defaultTz | ForEach-Object {
$timeZones[$_.IPAddress] = $_.TimeZone
}
Next, use these values something like this (demo uses a centralized error log file):
$errorlog = '\\Server\Share\Folder\TimezonesErrors.log'
$now = (Get-Date).ToString() # use default format or specify the date format yourself here
$currentTz = (Get-TimeZone).Id # get the current timezone Id
# get the default gateway IPv4 address for this computer
$gw = #((Get-wmiObject Win32_networkAdapterConfiguration | Where-Object {$_.IPEnabled -and $_.DefaultIPGateway-like '*.*.*.*'}).DefaultIPGateway)[0]
# check if the gateway IP address is present in the lookup Hashtable
if ($timeZones.ContainsKey($gw)) {
$tz = $timeZones[$gw]
# only set the wanted timezone if it differs from the current timezone
if ($tz -ne $currentTz) {
try {
Set-TimeZone -Id $tz -ErrorAction Stop
}
catch {
# add the exception to the error log
$msg = "$now - Error setting the timezone on computer $($env:COMPUTERNAME): $_.Exception.Message"
Add-Content -LiteralPath $errorlog -Value $msg
}
}
}
else {
# make it known that the IP address for this gateway was not found in the Hashtable
$msg = "$now - No timezone found for default gateway $gw on computer $($env:COMPUTERNAME)"
# write error to the central error.log file
Add-Content -LiteralPath $errorlog -Value $msg
}

select-string not taking variable in powershell

I have stored a MAC address as a string in a variable:
$macaddress= "1234567"
I'm trying to store the output from a command in another variable:
$testing = { arp -a; | select-string $macaddress }
Write-Host $testing
If the command is executed in PowerShell, I do not see the value of $macaddress. It displays as a '$macaddress' in the screen instead of its value "1234567".
Please help me set the value of $macaddress correctly.
The problem is not how you define variable $macaddress, but in how you try to capture command output.
Remove the enclosing { ... } and the ;:
$testing = arp -a | select-string $macaddress
As for what you tried:
{ ... } creates a script block, which is a piece of source code (loosely speaking) for later execution (e.g., with operators . or &).
If you pass a script block to Write-Host - whose use you should generally avoid, by the way - it is converted to a string, and the string representation is the literal contents of the script block between { and } - that's why you saw $macaddress appear (unexpanded) in your output.
; terminates a command, and it is only necessary if you place multiple commands on a single line.
A pipeline is still considered a single command, even though it is composed of multiple sub-commands; do not attempt to use ; in a pipeline - you'll break it (and, in fact, even your script-block-creating command would break).
Try it this way:
$macAddress = "00-01-02-03-04"
arp -a | Select-String $macAddress
If you want to extract the IP address related to the MAC address, you can do this:
$macAddress = "00-01-02-03-04"
arp -a | Select-String ('\W*((?:[0-9]{1,3}\.){3}(?:[0-9]{1,3}))\W+(' +
$macAddress + ')') | ForEach-Object { $_.Matches[0].Groups[1].Value }

Dynamically use variable in PowerShell

I have some predefined variables, eg:
$s1 = a
$s2 = b
...
$s[x] = x
I have a script that connects to a device via serial port, get some data, split the string and get a value I need.
The value that I have from the string I needed to use it to get a predefined variable.
In php I do it easily like this:
$var1 = a
$var2 = b
...
$var[x] = x
$i = something
echo ${"var_name$i"}
How can I do the same in PowerShell?
I tried
write-host $s$i
write-host $s{$i}
write-host ${s$i}
write-host ${"s$i"}
with no luck...all I get is either the value of $i or empty space
Use the Get-Variable cmdlet:
Write-Host $(Get-Variable "s$i" -ValueOnly)
You are looking for the Get-Variable cmdlet:
Get-Variable 'i'

Is there a one-liner for using default values with Read-Host?

I've written something like this to specify default values for prompts.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
if ($prompt -eq "") {} else {
$defaultValue = $prompt
}
Can it be shortened further?
Here is my attempt.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
if (!$prompt -eq "") {$defaultValue = $prompt}
I want a one-liner, so I'm gonna hold out accepting an answer until then.
N.b. $defaultValue should be stored independently of the one liner. Similar to the example above.
I've accepted the answer which lead me to the solution I was looking for.
$defaultValue = 'default'
if (($result = Read-Host "Press enter to accept default value $defaultValue") -eq '') {$defaultValue} else {$result}
And for those of you asking why. The reason is because it is easier on the eyes of whoever comes after me. Less is always more, when clarity is not sacrificed. IMHO.
EDIT;
Instead of a single line, perhaps I should have said a single phrase?
I've added this edit clarify whilst a few answers I have seen use are using a semi-colon.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
If you absolutely have to have it in one line:
$defaultValue = 'default'
($defaultValue,(Read-Host "Press enter to accept the default [$($defaultValue)]")) -match '\S' |% {$prompt = $_}
Shortest Version I could came up with:
if (!($value = Read-Host "Value [$default]")) { $value = $default }
This version doesn't have to use else.
if(($result = Read-Host "Press enter to accept default value [default]") -eq ''){"default"}else{$result}
$DefaultValue="Foobar"
.... (Optional other code) ....
$Value=if($Value=(Read-Host "Enter value [$DefaultValue]")){$Value}else{$DefaultValue}
Just threw this together to re-use a previously entered config value while still allowing user to change it if needed... The accepted answer is missing the assignment portion and uses a hardcoded "Default" value...
There is a function (from other languages) called "Ternary Operator" or "Binary Operator"(https://en.wikipedia.org/wiki/%3F:) (the 'xx : yy ? zz' style one) and there are several people who have submitted functions to implement its behavior in Powershell.
For me this is the shortest, easiest to use, and doesn't repeat the variable value or name:
$ChosenValue = 'Default Value' | %{ If($Entry = Read-Host "Enter a value ($_)"){$Entry} Else {$_} }
The magic sauce is the |%{} that pipes the default value to ForEach, allowing repeating the default value (aliased as $_) in the message and return value. It also depends on the fact that the assignment operator returns the value assigned for the purposes of the If statement. This assigned value will be treated as $true when an entry is provided, or $false(empty) when the [Enter] key is pressed instead.
Thanks go to #ojk's excellent answer which I considered the easiest to use and read, but I agree with #bluekeys that it's better to avoid repeating the default value and assigned variable name. Having the intermediate $Entry variable hasn't proven to be an inconvenience.
I use this method frequently to define function or script parameters with defaults that can be overridden at run time:
param(
$hostname = ('google.com' | %{ If($Entry = Read-Host "Host name ($_)"){$Entry} Else {$_} })
)
Write-Host $hostname
Running:
Host name (google.com):
google.com
Host name (google.com): facebook.com
facebook.com
Also, as #E.Sundin pointed out, Powershell 7 finally has a ternary operator that makes this syntax much simpler. Unfortunately many of us work on legacy systems that won't get version 7 in the near future.
EDIT: here's a slightly shorter version without an intermediate $Entry variable:
$hostname = 'google.com' | %{(Read-Host "Host name ($_)"),$_} | ?{$_} | Select -First 1
It makes an array containing the Read-Host response and the default value, then filters to exclude empty values, and selects the first (there would be two if the user responded).
$prompt = ( ($defaultValue='a_default_value'), (Read-Host "Please enter something [$defaultValue]")) -match '\S' | select -last 1
The given answers doesn't satisfy me (and don't work for me) but gave me enough input to came up with the shortest one-liner.
if (($prompt = Read-Host -Prompt "Your Text [your default]") -eq "") {$prompt= "your default"}
Why adding an else? If user inputs nothing it does $prompt="your default" (could be a variable of course). If he adds something, its stored in %prompt already and you can leave the statement.
I am using a function for that:
Function Read-HostDefault($Prompt, $Default) {
if ($default) {
$prompt = "$prompt [$default]"
}
$val = Read-Host $prompt
($default,$val)[[bool]$val]
}
Which gives:
PS C:\Windows\system32> Read-HostDefault "Enter port" 5432
Enter port [5432] :
5432
PS C:\Windows\system32> Read-HostDefault "Enter port" 5432
Enter port [5432] : 1234
1234
PS C:\Windows\system32> Read-HostDefault "Enter port"
Enter port : 2468
2468
You could also use a switch statement in a single line like this:
param([string]$myVariable = $($($val = $(Read-Host "Enter value")); $(Switch -regex ($val) { ".+" { $val } default { "my default value" } })))
The -regex .+ will match one or more characters, this would include any white space characters so maybe you want to strip out white space when doing the switch match i.e. \S.+.
Assuming you already have $defaultValue:
$newValue = if ($value = Read-Host -Prompt "Please enter a value ($defaultValue)") { $value } else { $defaultValue }
This should work

perform nslookup from PowerShell

I'm writing a powershell to exact the ip from a server name, which need me to embed the nslookup code into my powershell
how can I do the intergrating work?
Can any body help me?
Add-PSSnapin Microsoft.SharePoint.PowerShell
$web = Get-SPWeb -Identity “http://nycs00058260/sites/usitp“
$server_status = "PROD"
$list=$web.Lists[”DNS_Status”]
$items = $list.items
Foreach($item in $items){
$item_name = $item["Server_name"] #need to get the ip by this name
/*nslook up*/
$item_name.update()
}
If you install the PSCX module, it comes with a cmdlet Resolve-Host which handles name lookups.
Absent that, this one-liner will do the job
[System.Net.Dns]::GetHostAddresses("www.msn.com")
You can also pass in an IP address - but the results will be different.
See also http://blogs.msdn.com/b/powershell/archive/2006/06/26/647318.aspx & http://powershell.com/cs/media/p/210.aspx
PowerShell 3.0 on Windows 8 and higher comes with a Resolve-DnsName cmdlet that will get this information:
(Resolve-DnsName $server_name)[0].IpAddress
Simply use :
Resolve-DnsName monServer | ? { # make selection here } | % { $_.IPAdress } | select-object -first 1
#Here is a far better method for nslookup
# HOWTO ensure an nslookup results no errors but still gives the original names and column separations
$day = Get-Date -Format yyyyMMdd #<-- HOWTO set the day variable for today
$ErrorActionPreference = ‘SilentlyContinue’ #<-- HOWTO turn off error messages
$WarningActionPreference = 'SilentlyContinue' #<-- HOWTO turn off warning messages
$servers = Get-Content .\input\servers.txt
Foreach ($server in $servers){
$result = Resolve-DnsName $server -Server $env:LOGONSERVER.Remove(0,2) -Type ALL #<-- NSLOOKUP using your logon server
write-host ($server+","+$result.Name+","+$result.IPAddress) #<-- HOWTO Write two variables separated by a comma
}
$ErrorActionPreference = ‘SilentlyContinue’ #HOWTO turn on error messages
$WarningActionPreference = 'SilentlyContinue' #HOWTO turn on warning messages