Remove space between objects when using Write-Host - powershell

I have a text file that has something like this:
Year= YYYY
Month= MM
I then read the file in, select the line I want, split the line so I only get what is to the right of the "=" sign, trim blank spaces, and put it all into a variable with this code:
$Year=Get-Content -Path .\test.txt | Where-Object {$_ -match 'Year='}
$Year=($Year -Split 'Year=').trim()
The above code is repeated for each setting I have (i.e. Year, Month, Day, Time).
When I put $Year into the command prompt I get "YYYY" (No leading space); but when I use Write-Host I get " YYYY" (has a leading space).
It is important that I be able to put $Year $Month next to eathother without a space for filepath and file naming reasons. (i.e. .\YYYY\MM or YYYYMM.txt).
I have tried:
Write-Host "Example: $Year\$Month"
Write-Host "Example: " $Year "\" $Month
Write-Host ("Example: $($Year)\$($Month)")
Write-Host "Example: $Year\" + $Month"
Write-Host "Example: $Year\" -f $Month"
....and several ways of implementing .trim() with no success.
If the problem is not in the Write-Host line, then the only other thing I can think of is that it must be with how the data is written into the variables. Please keep in mind, this is only an example, my actual problem is larger. This is just a simple example of what I need to accomplish.
QUICK UPDATE:
I found a long workaround like this:
Write-Host "$Year\" -nonewline
Write-Host "$Month\".trim() -nonewline
Write-Host "$Day\".trim() -nonewline
BUT there has to be a better way to do this. I am using about 8 variables in my actual code. So to give 1 example of a filename and path would be 8 lines long...which would be dumb.

The problem is that $Year -Split 'Year=' returns 2 elements, not just the string after Year=.
An immediate fix would be:
$Year = ($Year -Split 'Year=')[1].Trim()
More generally, you could write:
$Year = ($Year -split '=', 2)[1].Trim()
As for what you tried:
If the -split operator finds the separator string at the very start of the string too, it assumes that what precedes it is the first substring to return, which is the empty string in this case.
In your case, 'Year= YYYY' -split 'Year=' effectively yields array '', ' YYYY', and calling .Trim() on it trims the individual elements, so you end up with '', 'YYYY'.
If you print that to the console via PowerShell's implicit output, you can get an empty line, followed by YYYY, which is also what you would get if you used Write-Output $Year.
By contrast, if you use Write-Host, PowerShell stringifies the array by concatenating its elements with spaces (by default, can be changed with $OFS), so you got  YYYY.
To put it differently:
PS> Write-Host ('', 'YYYY')
YYYY # concatenation of: empty string, space, 'YYYY'
is by default implicitly the same as:
PS> Write-Host (('', 'YYYY') -join ' ')
YYYY

Related

From output, include line only contain a key word and extract first field from the included lines with powershell

With PowerShell, I am trying to extract the first field from an output that contains multiple lines as below. Along with this, I wanted to exclude if the line doesn't have a key 'web:'
Getting apps in org SomeOrg / space Somespace as x-user...
name requested state processes routes
maskedappname1 started web:1/1 maskedappname1.com
maskedappname2 started web:0/1 maskedappname2.com
maskedappname3 started web:1/1 maskedappname3.com
maskedappname4 started web:1/1 maskedappname4.com
maskedappname5 started web:1/1 maskedappname5.com
maskedappname6 stopped web:0/1 maskedappname6.com
after execution, my final output should be
maskedappname1
maskedappname2
maskedappname3
maskedappname4
maskedappname5
maskedappname6
tried multiple ways didn't help me.
Much appreciate it if I get some help on this.
Thanks.
You can use a switch with the -Regex parameter to match any line having web: and capture the everything from the beginning of the line until the first whitespace.
switch -File path\to\file.ext -Regex {
'(^\S+).+web:' { $Matches[1] }
}
See https://regex101.com/r/fxQtcN/1 for details.
iterate through each line
$array = $textWithMultipleLines.Split(“`n”)
foreach ($line in $array){
# ...
}
take fixed length (if possible) or split on space ant take the first item of the split array
($extract -split " ")[0]
# or the regex way:
$extract -replace '^([^ ]+ ).+$','$1'
all together
$array = $textWithMultipleLines.Split(“`n”)
foreach ($line in $array){
$maskedAppName = ($line -split " ")[0]
Write-Host "maskedAppName: $maskedAppName"
}

Is there a way to replace the first two occurrences of a value in a string?

I am going to be as clear with my question as I can.
I might be missing something very obvious here but I just don't know how to find a solution...
I have a string and I would like to replace the first two occurrences of ":" with "/":
String:
$string = 2020:10:07 08:45:49
Desired String:
2020/10/07 08:45:49
I have tried using .Replace as seen below:
$string = $string.Replace([regex]":","/",2)
But I am given this error every time:
Cannot find an overload for "replace" and the argument count: "3".
I have seen others use .Replace in this way before so I'm not sure what is so different about my usage. Can anyone point me in the right direction?
PowerShell is .net-based language.
String does not have overload method Replace with anything like count argument in .Net, but Python's string does.
You can use this:
$string = '2020:10:07 08:45:49'
#Replace 2+ spaces you have in source with single space
$string = $string -replace '\s+', ' '
# Variant 0 - Best - ALWAYS use proper types. There is DateTime type to use for Dates and Times!
#Exact parse by format to DateTime object
$dt = [DateTime]::ParseExact($string, 'yyyy:MM:dd hh:mm:ss', [System.Globalization.CultureInfo]::InvariantCulture)
#Convert DateTime to String
$result = $dt.ToString('yyyy\/MM\/dd hh:mm:ss')
.Net's String.Split has optional parameter count that means split no more than into # pieces. You can use it:
# Variant1
$result = [string]::Join('/',$string.Split(':', 3))
# Variant2
$result = $string.Split(':', 3) -join '/'
String.Replace() does not support regex patterns, nor does it accept a maximum count.
Use the -replace regex operator instead:
$string = '2020:10:07 08:45:49'
$string -replace '(?<=^[^:]*):(.*?):','/$1/'
This will replace only the first and second occurrence of : with /
Specifically for date/time representations, you may want to parse it as such, at which point you can easily re-format it:
$string = '2020:10:07 08:45:49'
$datetime = [datetime]::ParseExact($string, "yyyy:MM:dd HH:mm:ss", $null)
# Now we can create a new string with the desired format
Get-Date $datetime -Format 'yyyy/MM/dd HH:mm:ss'
# This might be easier than figuring out regex patterns
'{0:dd/MMM/yyyy-HH.mm.ss}' -f $datetime

If/Then - then executing even when condition is false

The below is supposed to go through a folder containing jpgs and replace the filenames with nnnZ.jpg, skipping a number if the image is in landscape.
I've seen the examples at https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-if?view=powershell-7.1 and it doesn't look like there's a problem with syntax of the if statement or the condition ($w -gt $h), but the 'then' is being executed every time, even though all but one of the images in the folder are in portrait. The values of $w and $h are correct (per the echo) so I don't understand what is going wrong.
Edit: or are those values actually strings, and is that what the %s mean?
Get-ChildItem | foreach {
$w=$(magick identify -format '%w' $_)
$h=$(magick identify -format '%h' $_)
$flag="false"
if ($w -gt $h) {
$c++
$flag="true"
}
$newname = "$($c)Z.jpg".Padleft(8,'0')
Rename-Item $_ -NewName $newname
$c++
echo $w $h $flag $c
}
In order not to have yet another 'unanswered' question on SO, here my comment as answer:
the code for $w=$(magick identify -format '%w' $_) and $h=$(magick identify -format '%h' $_) produces strings, not integer values.
Cast these to int using
[int]$w = $(magick identify -format '%w' $_)
[int]$h = $(magick identify -format '%h' $_)
so the comparison would do what you expect.
As mklement0 commented, in the above lines, the SubExpression operator $() is not needed, although it doesn't harm in getting the results.
Basically, while a simple ( ) grouping expression means 'execute this part first', a subexpression $( ) means 'execute this first and then treat the result like a variable'. It can contain multiple semicolon ; separated statements.
If the files you need to get the width and height for are all of type BMP, GIF, JPEG, PNG or TIFF, you don't need to use an external application like ImageMagick to get the size, because the .NET System.Drawing.Image class can handle those very well:
# open the image file
$img = [System.Drawing.Image]::FromFile($_.FullName)
$w = $img.Width # these are Int32 values
$h = $img.Height
# dispose of this image object
$img.Dispose()

Powershell replace last two occurrences of a '/' in file path with '.'

I have a filepath, and I'm trying to remove the last two occurrences of the / character into . and also completely remove the '{}' via Powershell to then turn that into a variable.
So, turn this:
xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx
Into this:
xxx-xxx-xx\xxxxxxx\x\xxxx-xxxxx-xxxx.xxxxx.xxxxx
I've tried to get this working with the replace cmdlet, but this seems to focus more on replacing all occurrences or the first/last occurrence, which isn't my issue. Any guidance would be appreciated!
Edit:
So, I have an excel file and i'm creating a powershell script that uses a for each loop over every row, which amounts to thousands of entries. For each of those entries, I want to create a secondary variable that will take the full path, and save that path minus the last two slashes. Here's the portion of the script that i'm working on:
Foreach($script in $roboSource)
{
$logFileName = "$($script.a).txt".Replace('(?<=^[^\]+-[^\]+)-','.')
}
$script.a will output thousands of entries in this format:
xxx-xxx-xx\xxxxxxx\x{xxxx-xxxxx-xxxx}\xxxxx\xxxxx
Which is expected.
I want $logFileName to output this:
xxx-xxx-xx\xxxxxxx\x\xxxx-xxxxx-xxxx.xxxxx.xxxxx
I'm just starting to understand regex, and I believe the capture group between the parenthesis should be catching at least one of the '\', but testing attempts show no changes after adding the replace+regex.
Please let me know if I can provide more info.
Thanks!
You can do this in two fairly simply -replace operations:
Remove { and }
Replace the last two \:
$str = 'xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
$str -replace '[{}]' -replace '\\([^\\]*)\\([^\\]*)$','.$1.$2'
The second pattern matches:
\\ # 1 literal '\'
( # open first capture group
[^\\]* # 0 or more non-'\' characters
) # close first capture group
\\ # 1 literal '\'
( # open second capture group
[^\\]* # 0 or more non-'\' characters
) # close second capture group
$ # end of string
Which we replace with the first and second capture group values, but with . before, instead of \: .$1.$2
If you're using PowerShell Core version 6.1 or newer, you can also take advantage of right-to-left -split:
($str -replace '[{}]' -split '\\',-3) -join '.'
-split '\\',-3 has the same effect as -split '\\',3, but splitting from the right rather than the left.
A 2-step approach is simplest in this case:
# Input string.
$str = 'xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
# Get everything before the "{"
$prefix = $str -replace '\{.+'
# Get everything starting with the "{", remove "{ and "}",
# and replace "\" with "."
$suffix = $str.Substring($prefix.Length) -replace '[{}]' -replace '\\', '.'
# Output the combined result (or assign to $logFileName)
$prefix + $suffix
If you wanted to do it with a single -replace operation (with nesting), things get more complicated:
Note: This solution requires PowerShell Core (v6.1+)
$str -replace '(.+)\{(.+)\}(.+)',
{ $_.Groups[1].Value + $_.Groups[2].Value + ($_.Groups[3].Value -replace '\\', '.') }
Also see the elegant PS-Core-only -split based solution with a negative index (to split only a fixed number of tokens off the end) in Mathias R. Jessen's helpful answer.
try this
$str='xxx-xxx-xx\xxxxxxx\x\{xxxx-xxxxx-xxxx}\xxxxx\xxxxx'
#remove bracket and split for get array
$Array=$str -replace '[{}]' -split '\\'
#take all element except 2 last elements, and concat after last elems
"{0}.{1}.{2}" -f ($Array[0..($Array.Length -3)] -join '\'), $Array[-2], $Array[-1]

Count number of spaces, and split at the last one

I have a string simliar to:
c:/folder name/somewhere/application.exe instanceName
(n.b. the space in "folder name" is intentional) I need a way to split this into:
[0]c:/folder name/somewhere/application.exe
[1]instanceName
I was going to use split-path, but apparently there is a bug in powershell v2 that stops me doing this:
Split-Path : Cannot find drive. A drive with the name '
So, I figured if I count how many spaces there are, and then simply use -split() to split it at the last space.
But, I can't see how to count the number of spaces.
I've found lots of examples that talk about using regex to count complex strings, but I just want to count spaces!
Tonnes of ways to do this I imagine but to use your split idea you could do the following.
$split = "c:/folder name/somewhere/application.exe instanceName".Split(" ")
$path = $split[0..($split.count -2)] -Join " "
$instance = $split[-1]
Split the sting by spaces. The number of spaces is represented by the count of strings in the array $split. We join all the strings in the array accept the last intp $path then we take the last entry and assign it to $instance
You could also use .substring and .lastindexof
$string = "c:/folder name/somewhere/application.exe instanceName"
$index = $string.LastIndexOf(" ")
$string.Substring(0,$index)
$string.Substring($index + 1)
I can't see a way to split this directly into an array at this time but outputing as an array would not be a big deal.
$path, $instance
or
$array = #($path,$instance)
You can use a regular expression for this:
$s = "c:/folder name/somewhere/application.exe instanceName"
$s -match '(.*) (.*)$'
$matches[1]
$matches[2]
The special variable $matches is populated if the -match operation is true.
$matches[0] contains the original string, and other indexes will exist for the number of groups (patterns in parenthesis) in the regex. In this case: (.*) (.*)$ we have two groups.