Powershell : Extract Second line from String - powershell

I wish to select the thirdline of text from a sting in powershell.
The string I have is coming from the body of an email:
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
foreach ($email in $inbox.items){
if($email.Subject -eq "PASSED") {
$Body = $email.Body
write-host $emailSentOn
write-host $email.Body
}
}
The $Body string is formatted like so:
<BLANK LINE>
Queue Cleared!
Server Name
Username
I wish to exact the Server name from the 4 line text string.
Thanks

Assuming the format is consistent
if ($Body[2] -match '(?m:^Server (?<server>\w+))' ) {
# do something with $matches.server
}

Using a multi-line regex:
$body = #"
Queue Cleared!
Server Name
Username
"#
#$regex = '(?ms).+?$(.+)?$.+?$.+' #Capture second line.
$regex = '(?ms).+?$.+?$(.+)?$.+' #Capture third line.
$body -replace $regex,'$1'
Server Name
Each .+?$ represents one line of the body. The third line is captured and used for the -replace operation.
The title doesn't quite match the descripion in the post (second line vs third line) so I've included both.

You can split a string using the -split operator:
$foo -split "`n"
You'll get an array back. You can then get an element from that array using indexing:
($foo -split "`r?`n")[2]

This will send only the 3rd item of the array (server name) to the Body. The count starts at zero, so that's why [2] = 3rd.
$Body[2] = $email.Body
Body result
Server Name

Related

Sending email attachment - Converting CSV file to bytes removes line breaks

I'm trying to use the MS Graph cmdlet Send-MgUserMail to send an email and include a CSV attachment.
If I do it like this, it works fine, and the attachment is fine.
Sample CSV attachment
"App","AppId","Date","User"
"App1","43a9087d-8551-47d5-9869-3287736ce7c3","2023/01/26","me#email.com"
"App2","f33750cd-c79b-43be-8e55-ee14d163e2b3","2024/01/26","me#email.com"
Working code
$attachment = "C:\Users\Me\Downloads\SampleFile.csv"
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($attachment))
$params = #{
UserId = "me#email.com"
BodyParameter = #{
Message = #{
Subject = "Test Email"
Body = #{
ContentType = "Html"
Content = "This is sample text."
}
ToRecipients = #(
#{
EmailAddress = #{
Address = "toAddress#email.com"
}
}
)
Attachments = #(
#{
"#odata.type" = "#microsoft.graph.fileAttachment"
Name = "SampleFile.csv"
ContentType = "text/csv"
ContentBytes = $base64string
}
)
}
SaveToSentItems = "false"
}
}
Send-MgUserMail #params
Objective
What I would like is to attach the CSV, without having an actual saved file. The content would be from memory, like so.
$output = #(
[PSCustomObject] #{
App = "App1"
AppId = "43a9087d-8551-47d5-9869-3287736ce7c3"
Date = "2023/01/26"
User = "me#email.com"
},
[PSCustomObject] #{
App = "App2"
AppId = "f33750cd-c79b-43be-8e55-ee14d163e2b3"
Date = "2024/01/26"
User = "me#email.com"
}
)
$attachment = $output | ConvertTo-Csv -NoTypeInformation
$bytes = [System.Text.Encoding]::UTF8.GetBytes($attachment)
$base64string = [Convert]::ToBase64String($bytes)
My issue when I do this is the CSV content is entirely on 1 line, there's no more line breaks.
Issue Sample CSV Output
"App","AppId","Date","User" "App1","43a9087d-8551-47d5-9869-3287736ce7c3","2023/01/26","me#email.com" "App2","f33750cd-c79b-43be-8e55-ee14d163e2b3","2024/01/26","me#email.com"
How can I convert this properly?
Answer was given in comments but to give it closure, what happens is that ConvertTo-Csv outputs an array of strings and they don't have new line characters (CRLF in Windows, LF in Linux), before getting the bytes of your Csv you need to convert this array into a single multi-line string, for this you can use Out-String:
$attachment = $output | ConvertTo-Csv -NoTypeInformation | Out-String
Or join them by a new line character to preserve the structure:
$attachment = ($output | ConvertTo-Csv -NoTypeInformation) -join [Environment]::NewLine
Also, [System.Text.Encoding]::UTF8.GetBytes(...) which has a string type argument is coercing your array into a string and when you coerce an array into a string in PowerShell, it is joined by $OFS (a white space character by default) hence why you get a single line joined by whitespaces when decoding the base64 string.
Another way this could've been done is by assigning $OFS the value of a new line character:
$attachment = $output | ConvertTo-Csv -NoTypeInformation
$OFS = [System.Environment]::NewLine
$bytes = [System.Text.Encoding]::UTF8.GetBytes($attachment)
$base64string = [Convert]::ToBase64String($bytes)
$OFS = ' '
# Decoding should look as expected
[System.Text.Encoding]::UTF8.GetString(
[Convert]::FromBase64String($base64string)
)

ConvertFrom-StringData clear hash-table

I am attempting to parse multiple mails in a specific mail folder, to eventually create a csv file with the information.
If I run the script on one user, the output I get is perfect;
Name Value
---- -----
Kontor. HR
Prøvetid Ja
Blomster på dagen Nej
Telefonnr. 85789845
Adresse Hovedgade 13
Ansættes i stedet for Ninette Kock
Navn Marianne Pedersen
Etage 1. sal
Fri telefon Ja
Benyttet navn Marianne Pedersen
Medarbejdertype Fastansat
Overtagelse af mobilnr. Ja
Placering Sydøst
Ansættelsesdato 01-01-2020
Stilling Fuldmægtig
Lokalnr. +45 3370 0000
Besked til IT-centret
Antal timer 37
Journalistpraktikant Nej
Tidl. ansat Nej
Initialer MAPE
Blomster anden dag 02-01-2020
Postnr/By 2630 Taastrup
Cpr. nr. XXXX
The problem is when I try to parse a second mail, the first mail will still get parsed correctly, but the second one will throw an error;
ConvertFrom-StringData : Data item 'Medarbejdertype' in line 'Medarbejdertype=Fastansat' is
already defined.
If I try to parse the second mail as the first mail, it will parse perfectly.
Problem happens when I try to parse more then one.
I have tried to clear the hash-table like so;
$data.clear()
clear-variable -name data
$data = #{}
But it somehow seems that the hash-table is not emptied?
The code I am using:
Clear-Host
$navne = "MAJE", "ASHO"
# create an instance of Outlook
Add-Type -AssemblyName "Microsoft.Office.Interop.Outlook"
$outlook = New-Object -ComObject Outlook.Application
$nameSpace = $outlook.GetNameSpace("MAPI")
$html = New-Object -ComObject "HTMlFile"
# Opens and save all mails in Nye Til Oprettelse in $mails.
$mails = $nameSpace.Folders.Item('hotline').Folders.Item('Indbakke').Folders.Item('#HOVSA''er').Folders.Item('#01Nye til oprettelse').Items | Select-Object -Property subject, htmlbody
# Loop over $mails, check if $navn = subject
foreach ($navn in $navne) {
foreach ($mail in $mails) {
if($mail | Where-Object {$_.subject -match "$Navn"}) {
#$mail.HTMLBody
# write data to $html, then find all <table> tags and parse that text.
$html.IHTMLDOCUMENT2_write($mail.HTMLBody)
$data = $html.all.tags("table") | % innertext
$data = $data -split '\r?\n' -match '^[^:]+:[^:]+$' -replace ':\s*', '=' -join "`n" | ConvertFrom-StringData
$navn
$data
$data = #[]
}
}
}
You're appending each email to the $html document using $html.IHTMLDOCUMENT2_write($mail.HTMLBody), but you only initialise it once at the top of your script, so each time your foreach ($mail in $mails) loops you add another email to the document.
That means the second time the loop iterates there are two emails in the $html document and you're getting an error about duplicate data items as a result.
Try moving $html = New-Object -ComObject "HTMlFile" inside your foreach loop so it reads:
foreach ($mail in $mails) {
...
$html = New-Object -ComObject "HTMlFile"
$html.IHTMLDOCUMENT2_write($mail.HTMLBody)
...
}
P.S.: You might also want to look at using a non-COM library like the HTML Agility Pack to do your html processing - it'll potentially be a bit faster and more reliable (and portable) in an automation script than COM is.

Grab parameters from url and drop them to powershell variables

i have a powershell listener running on my windows-box. Code from Powershell-Gallery: https://www.powershellgallery.com/packages/HttpListener/1.0.2
The "Client" calls the listener with the following example url:
With Powershell i do:
invoke-webrequest -Uri "http://127.0.0.1:8888/Test?id='1234'&param2='##$$'&param3='This is a test!'"
I have no idea, how to drop the parameters from the url to variables with the same name in powershell. I need to bring the parameters to powershellvariables to simply echo them. this is my last missing part. The parameters are separated with & and parameternames are case-sensitive.
To be more detailed, the id from the url should be in a powershell-variable named $id with the value 1234. The Variables can contain spaces, special characters, numbers, alphas. They are case sensitive. The parametervalue could be "My Name is "Anna"! My Pets Name is 'Bello'. " with all the "dirty" Characters like '%$"!{[().
Can someone point me to the right way how to get this solved?
In a valid url, the $ characters should have been escaped to %24
The other 'dirty' character % in Url escaped form is %25
This means the example url is invalid and should be:
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
Then the following does work
$url = "http://127.0.0.1:8888/Test?id='1234'&param2='##%24%24'&param3='This is a test!'"
if ($url -is [uri]) {
$url = $url.ToString()
}
# test if the url has a query string
if ($url.IndexOf('?') -ge 0) {
# get the part of the url after the question mark to get the query string
$query = ($url -split '\?')[1]
# or use: $query = $url.Substring($url.IndexOf('?') + 1)
# remove possible fragment part of the query string
$query = $query.Split('#')[0]
# detect variable names and their values in the query string
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$varName = [uri]::UnescapeDataString($kv[0]).Trim()
$varValue = [uri]::UnescapeDataString($kv[1])
New-Variable -Name $varname -Value $varValue -Force
}
}
else {
Write-Warning "No query string found as part of the given URL"
}
Prove it by writing the newly created variables to the console
Write-Host "`$id = $id"
Write-Host "`$param2 = $param2"
Write-Host "`$param3 = $param3"
which in this example would print
$id = '1234'
$param2 = '##$$'
$param3 = 'This is a test!'
However, I personally would not like to create variables like this, because of the risk of overwriting already existing ones.
I think it would be better to store them in a hash like this:
# detect variable names and their values in the query string
# and store them in a Hashtable
$queryHash = #{}
foreach ($q in ($query -split '&')) {
$kv = $($q + '=') -split '='
$name = [uri]::UnescapeDataString($kv[0]).Trim()
$queryHash[$name] = [uri]::UnescapeDataString($kv[1])
}
$queryHash
which outputs
Name Value
---- -----
id '1234'
param2 '##$$'
param3 'This is a test!'

cutting a portion of url in powershell

I have a few URLs which would need to cut and separate the first part of the each URL, i.e example1.com, example2.com, example3.com from each line and store in a variable
Contents in url.csv
https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4
https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8
https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6
Script:
$oldurl = Import-CSV "url.csv"
$newurl = $oldurl.list -replace "https://"
This would replace https://, however the rest of each cannot be hard coded as those values can change.
What could be change code change required to cut anything from and after /v1/ along with https://?
$list = #(
"https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4",
"https://example2.com/v1/test/14nf-d7jc-54lf-fd90-fds8",
"https://example3.com/v1/test/bd38-17gd-2h65-0j3b-4jf6"
)
$result = $list | %{
$uri = [System.Uri] $_
$uri.Authority
}
$result
See System.Uri properties to potentially assemble the information you need in your result list.
This will cut off anything after "/v1/" and it self. Is that what you want?
$string = "https://example1.com/v1/test/f3de-a8c6-464f-8166-9fd4"
$string = $string -replace "https://"
$pos = $string.IndexOf("/v1/")
$result = $string.Substring(0, $pos)
$result
Output: example1.com

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone