How to output return value using Powershell? - powershell

I want to output the return of this process. Anyone can help me please. Thank you.
$name = $false
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } Out-File .\PN.txt}
}
I tried that way, but the output file is empty.

The Out-File .\PN.txt command is only ever reached for (a) lines that start with a non-whitespace character (\S) while (b) $name isn't $true.
When it is reached, it creates an empty .\PN.txt file, due to lack of input.
If, perhaps, your intent was to send all output from the switch statement to a file, try the following:
$name = $false
& {
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } $_ }
}
} | Out-File .\PN.txt

Related

Powershell - Exchange JSON output without needing to write to a file

EDIT: Added Setupconfigfiles.ps1
I'm a bit new to detailed scripting so please bear with me.
I have two Powershell scripts
Setupconfigfiles.ps1 generates JSON output to be fed to an API.
Script2 uses that JSON data to execute API commands.
Script 2 can call setupconfigfiles.ps1 as indicated below and use the output data.
.\SetupConfigFiles.ps1 -type $Type -outfile .\Templist.json
$servers = Get-Content -Raw -Path .\templist.json | ConvertFrom-Json
setupconfigfiles.ps1:
param (
# If this parameter is set, format the output as csv.
# If this parameter is not set, just return the output so that the calling program can use the info
[string]$outfile,
# this parameter can be 'production', 'development' or 'all'
[string]$type
)
enum MachineTypes {
production = 1
development = 2
all = 3
}
$Servers = Get-ADObject -Filter 'ObjectClass -eq "computer"' -SearchBase 'Obfuscated DSN' | Select-Object Name
$output = #()
$count = 0
# Set this to [MachineTypes]::production or [MachineTypes]::development or [MachineTypes]::all
if ($type -eq "all") {
$server_types = [MachineTypes]::all
}
ElseIf ($type -eq "production") {
$server_types = [MachineTypes]::production
}
else {
$server_types = [MachineTypes]::development
}
ForEach ($Server in $Servers)
{
$count = $count + 1
$this_server = #{}
$this_server.hostname = $Server.Name
$this_server.id = $count
$this_server."site code" = $this_server.hostname.substring(1,3)
$this_server."location code" = $this_server.hostname.substring(4,2)
if ($this_server.hostname.substring(7,1) -eq "P") {
$this_server.environment = "Production"
}
ElseIf ($this_server.hostname.substring(7,1) -eq "D") {
$this_server.environment = "Development"
}
Else {
$this_server.environment = "Unknown"
}
if (($server_types -eq [MachineTypes]::production ) -and ($this_server.environment -eq "Production")) {
$output += $this_server
}
ElseIf (($server_types -eq [MachineTypes]::development ) -and ($this_server.environment -eq "Development")) {
$output += $this_server
}
Else {
if ($server_types -eq [MachineTypes]::all ) {
$output += $this_server
}
}
}
if ($outfile -eq "")
{
ConvertTo-Json $output
}
else {
ConvertTo-Json $output | Out-File $outfile
}
How can I do it without needing to write to the Templist.json file?
I've called this many different ways. The one I thought would work is .\SetupConfigFiles.ps1 $servers
Y'all are great. #Zett42 pointed me in a direction and #Mathias rounded it out.
The solution was to change:
"ConvertTo-Json $output" to "Write-Output $output"
Then it's handled in the calling script.
thanks!

Comparing two text files and output the differences in Powershell

So I'm new to the Powershell scripting world and I'm trying to compare a list of IPs in text file against a database of IP list. If an IP from (file) does not exist in the (database) file put it in a new file, let's call it compared.txt. When I tried to run the script, I didn't get any result. What am I missing here?
$file = Get-Content "C:\Users\zack\Desktop\file.txt"
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
foreach($line1 in $file){
$check = 0
foreach($line2 in $database)
{
if($line1 != $line2)
{
$check = 1
}
else
{
$check = 0
break
}
}
if ($check == 1 )
{
$line2 | Out-File "C:\Users\zack\Desktop\compared.txt"
}
}
There is a problem with your use of PowerShell comparison operators unlike in C#, equality and inequality are -eq and -ne, and since PowerShell is a case insensitive language, there is also -ceq and -cne.
There is also a problem with your code's logic, a simple working version of it would be:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
# iterate each line in `file.txt`
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
# iterate each line in `database.txt`
# this happens on each iteration of the outer loop
$check = foreach($line2 in $database) {
# if this line of `file.txt` is the same as this line of `database.txt`
if($line1 -eq $line2) {
# we don't need to keep checking, output this boolean
$true
# and break the inner loop
break
}
}
# if above condition was NOT true
if(-not $check) {
# output this line, can be `$line1` or `$line2` (same thing here)
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
However, there are even more simplified ways you could achieve the same results:
Using containment operators:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
$result = foreach($line1 in Get-Content "C:\Users\zack\Desktop\file.txt") {
if($line1 -notin $database) {
$line1
}
}
$result | Set-Content path\to\comparisonresult.txt
Using Where-Object:
$database = Get-Content "C:\Users\zack\Desktop\database.txt"
Get-Content "C:\Users\zack\Desktop\file.txt" | Where-Object { $_ -notin $database } |
Set-Content path\to\comparisonresult.txt
Using a HashSet<T> and it's ExceptWith method (Note, this will also get rid of duplicates in your file.txt):
$file = [System.Collections.Generic.HashSet[string]]#(
Get-Content "C:\Users\zack\Desktop\file.txt"
)
$database = [string[]]#(Get-Content "C:\Users\zack\Desktop\database.txt")
$file.ExceptWith($database)
$file | Set-Content path\to\comparisonresult.txt

Powershell scripting - replace text in files

Given these powershell functions below. I'm trying to replace the version text with another version that I specify. All my paths are correct, and I output both the current version and new version appropriatlely. When i go to replace them, the file indicates that it has changed, but no text was changed.
Alternately, I've found if i specify the exact current version as a string variable and bypass the Get-VersionXXX calls, it replaces the version without issues. Is there anything obvious I'm doing wrong?
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
return $tokens[1]
}
}
}
}
return ""
}
function Get-VersionOnlyFromFile([string] $versionFile, [string] $versionString)
{
[string] $versionAndDate = Get-VersionAndDateFromFile $versionFile $versionStriong
return VersionFromVersionAndDateString $versionAndDate
}
function Get-VersionFromVersionAndDateString([string] $versionAndDateString)
{
if (!$versionAndDateString)
{
return ""
}
else
{
if ($versionAndDateString.Contains(" "))
{
$newTokens = $versionAndDateString.Trim().Split(" ")
return $newTokens[0]
}
else
{
return $versionAndDateString
}
}
}
function ReplaceTextInFile([string] $fullPath, [string] $oldString, [string] $newString)
{
Write-Host "Old " $oldString
Write-Host "New " $newString
((Get-Content -path $fullPath -Raw) -replace $oldString,$newString) | Set-Content -Path $fullPath
}
Calling code:
[string] $newVersionString = "1.2.3.4.6 09-16-2021"
[string] $currentVersionString = Get-VersionAndDateFromFile $pathToVersionFile "SW_VERSION"
ReplaceTextInFile -fullPath $pathToVersionFile -oldString $currentVersionString -newString $newVersionString
finally: file is a header file called Version.h
#define SW_VERSION "1.2.3.4.5 09-14-2021"
Unless explicitly redirected all output in a function is returned to the pipeline. The return command is redundant. As such, you're sending back two things in your function, first is the version number and second is an empty string (lines starting with >>>):
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
>>> return $tokens[1]
}
}
}
}
>>> return ""
}
You convert the output to a string, and by all accounts it looks right, but if you look more closely you'll see there's a space after the date:
PS C:\WINDOWS\system32> ">$currentVersionString<"
>1.2.3.4.5 09-14-2021 <
If we comment out the last return in that function the space goes away, and your replace should work fine.
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (-Not(Test-Path -path $versionFile))
{
return ""
}
else
{
#foreach($line in [System.IO.File]::ReadLines($versionFile))
(Get-Content -Path $versionFile -Raw) | ForEach-Object {
$line = $_
if ($line.Contains($versionString))
{
if ($line.Contains(""""))
{
$tokens = $line.Split('\"')
return $tokens[1]
}
}
}
}
# return ""
}
To be honest, it would be much simpler to do this:
function Get-VersionAndDateFromFile([string] $versionFile, [string] $versionString)
{
if (Test-Path -path $versionFile)
{
Get-Content -Path $versionFile -Raw | Where-Object{$_ -match "$versionString\s+""(.+?)"""} | ForEach-Object {
$Matches[1]
}
}
}
Run your Replace Function with these 2 added lines, as shown below...
$old = $oldString -replace '\s','\s';
$old = $old -replace '\s$';
You'll find you're capturing an extra space at the end of your OldString variable. Like so: 1.2.3.4.5\s\s09-16-2021\s
The two lines fix the issue and it does the replace... or you can fix your Get-Version function to capture the correct string.
function ReplaceTextInFile([string] $fullPath, [string] $oldString, [string] $newString)
{
Write-Host "Old " $oldString
Write-Host "New " $newString
$old = $oldString -replace '\s','\s'; #replaces space with \s
$old = $old -replace '\\s$'; #removes trailing space at end of line
$old = $oldString -replace '\s','\s'; $old = $old -replace '\\s$'
((Get-Content -path $fullPath -Raw) -replace $old,$newString) | Set-Content -Path $fullPath
}

How do I access object properties in a dynamic manner?

I have, what in my opinion is, bad-looking code, as i currently update properties on a case-by-case basis through the use of a switch statement. Instead, I would like to dynamically update a property if a $Key by the same name as the property can be found in $PSBoundParameters. Note that each given $Key is assumed to also exist as a property in the object InputObject.
My current solution:
foreach ($Key in $PSBoundParameters.Keys) {
switch ($Key) {
{ $_ -Match "^TimeToLive$"} { $InputObject.RecordData.TimeToLive = $PSBoundParameters[$Key] }
{ $_ -Match "^AllowUpdateAny$"} { $InputObject.RecordData.AllowUpdateAny = $PSBoundParameters[$Key] }
{ $_ -Match "^IPv4Address$"} { $InputObject.RecordData.IPv4Address = $PSBoundParameters[$Key] }
{ $_ -Match "^IPv6Address$"} { $InputObject.RecordData.IPv6Address = $PSBoundParameters[$Key] }
{ $_ -Match "^HostNameAlias$"} { $InputObject.RecordData.HostNameAlias = $PSBoundParameters[$Key] }
{ $_ -Match "^PtrDomainName$"} { $InputObject.RecordData.PtrDomainName = $PSBoundParameters[$Key] }
{ $_ -Match "^MailExchange$"} { $InputObject.RecordData.MailExchange = $PSBoundParameters[$Key] }
{ $_ -Match "^Preference$"} { $InputObject.RecordData.Preference = $PSBoundParameters[$Key] }
{ $_ -Match "^DomainName$"} { $InputObject.RecordData.DomainName = $PSBoundParameters[$Key] }
{ $_ -Match "^Priority$"} { $InputObject.RecordData.Priority = $PSBoundParameters[$Key] }
{ $_ -Match "^Weight$"} { $InputObject.RecordData.Weight = $PSBoundParameters[$Key] }
{ $_ -Match "^Port$"} { $InputObject.RecordData.Port = $PSBoundParameters[$Key] }
}
}
Pseudocode for what I want my solution to look like:
For each $Key in $PSBoundParameters
Set $InputObject.RecordData property of name $Key to value of current key / value pair
Any improvement to my current solution is much appreciated. Thank you.
You'll need a list of parameter names to filter against, at which point you can simplify your loop to:
$RecordDataPropertyNames = 'TimeToLive', 'AllowUpdateAny', 'IPv4Address', 'IPv6Address', 'HostNameAlias', 'PtrDomainName', 'MailExchange', 'Preference', 'DomainName', 'Priority', 'Weight', 'Port'
# ...
foreach($key in $PSBoundParameters.Keys |Where {$_ -in $RecordDataPropertyNames}){
$InputObject.RecordData.$key = $PSBoundParameters[$key]
}

Return from function

I wish to return from the following powershell function if I find a match (for a more complete code sample see my codereview question):
Function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)){ return $_ }
}
}
This code just appears to return from the scope of the if conditional, which is not so useful.
How can I do this? Do I need a labeled break somewhere?
If you wish to use the return statement to exit the function you can use the foreach keyword instead of the ForEach-Object cmdlet. Here's a demo:
function Foo {
foreach ($number in (0..287)) {
$number # Just show our current iteration.
if ($number -eq 50) {
return $number
}
}
}
No need for a label.
function Find-Property($Filename, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Where {$PropertyName -eq $shellfolder.GetDetailsOf($null, $_)} |
Foreach {$_;break}
}
Another option is to minorly tweak your original function:
function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)) {$_; break}
}
}