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

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)
)

Related

SMLETS: Powershell

We want to generate an SR per row based on the criteria of a CSV file looking like:
SR templete
The additional criterion:
If the SLO countdown is less than 7 days then the due date is always 7 days for the ticket to be due. Otherwise then then countdown is number SLO _Countdown
The support group is always servicedesk
Unless the host_name does not contain "RES" then it is the support group is EITS_HW_Notes and it will be assigned to "custodian".
No matter what an SR is generated even if null.
My difficulty is my lack familiarity with smlets. I am happy to consider generating tickets via email as well. But would like help on how best to do that via powershell. But the code I came up with is below:
`#Prod
#$GLOBAL:smdefaultcomputer = "prodserver"
#Test
$GLOBAL:smdefaultcomputer = "testserver"
Import-Module SMlets
$path = "C:\Temp\Test.csv"
$csv = Import-csv -path $path
#Variable / Class Setup
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
$srprior = Get-SCSMEnumeration -Name ServiceRequestPriorityEnum.Medium
$srurg = Get-SCSMEnumeration -Name ServiceRequestUrgencyEnum.Medium
#$ararea = get-SCSMEnumeration -Name ServiceRequestAreaEnum.Other
$ararea = get-SCSMEnumeration -Name Enum.add3768303064ec18890170ba33cffda
$title = “Title Goes Here”
$descrip = "Description info goes here"
#Service Request Arguements
$srargs = #{
Title = $title;
Urgency = $srurg;
Priority = $srprior;
ID = “SR{0}”;
Area = $ararea;
SupportGroup = "ServiceDesk";
Description = $descrip
}
#Create Service Request
$newServiceRequest = New-SCSMOBject -Class $srClass -PropertyHashtable $srargs -PassThru
#get SR ID of the new object
$SRId = $newServiceRequest.id
#Get Projection & Object for Created Service Request
$srTypeProjection = Get-SCSMTypeProjection -name System.WorkItem.ServiceRequestProjection$
$SRProj = Get-scsmobjectprojection -ProjectionName $srTypeProjection.Name -filter “Id -eq $SRId”
#Set Afffected User
$userClass = Get-SCSMClass -Name Microsoft.AD.UserBase$
$cType = "Microsoft.EnterpriseManagement.Common.EnterpriseManagementObjectCriteria"
$cString = "UserName = 'itservicenotifications' and Domain = 'SHERMAN'"
$crit = new-object $cType $cString,$userClass
$user = Get-SCSMObject -criteria $crit
$AffectedUserRel = get-scsmrelationshipclass -name System.WorkItemAffectedUser$
New-SCSMRelationshipObject -RelationShip $AffectedUserRel -Source $newServiceRequest -Target $user -Bulk`
I tried the above code but am running into issues recognizing the column name in the CSV file and am unfamiliar with SMLETS + powershell if statements.
Columns are:
CSV Columns
CSV text with examples is: Columns with examples
Could you paste the CSV columns as text, please? Or, better, a sample CSV with one or two rows (redact any sensitive data).
I would expect a CSV to contain multiple rows - even if yours does not, it's good defensive programming to act as if it does. So the first modification I suggest is:
$path = "C:\Temp\Test.csv"
$csv = Import-csv -path $path
foreach ($Row in $csv)
{
# the rest of your code goes in here
}
I find it helpful while debugging to go step-by-step. If I understand your problem right, it's about building the right hashtable in $srargs to pass to New-SCSMOBject. So the next modification is:
foreach ($Row in $csv)
{
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
# etc
$srargs = #{
Title = $title
Urgency = $srurg
Priority = $srprior
ID = “SR{0}”
Area = $ararea
SupportGroup = "ServiceDesk"
Description = $descrip
}
$srargs # write the hashtable so you can inspect it
# skip the rest of the code for now
}
I understand your question as "how to express the logic of":
support group is always servicedesk
Unless the host_name does not contain "RES"
then the support group is contents of EITS_HW_Notes cell in CSV
and it will be assigned to "custodian"
I can't help you with setting the assignee. But we can rejig the rest of the statement:
if host_name contains "RES"
SupportGroup = servicedesk
else
SupportGroup = contents of EITS_HW_Notes cell
You can code that like this:
foreach ($Row in $csv)
{
$srClass = Get-SCSMClass -name System.WorkItem.ServiceRequest
# etc
if ($Row.host_name -like "*RES*")
{
$SupportGroup = "ServiceDesk"
}
else
{
$SupportGroup = $Row.EITS_HW_Notes
}
$srargs = #{
Title = $title
# etc
SupportGroup = $SupportGroup
Description = $descrip
}
}
Does that get you any closer to your solution?

Powershell For Loop for multiple servers - to get SSAS connection string details

I am very new to powershell script. i am trying to get SSAS Tabular model connection string details for multiple servers. i have code which will return only for single server. How to modify the code to pass multiple servers?
$servername = "servername1"
# Connect SSAS Server
$server = New-Object Microsoft.AnalysisServices.Server
$server.connect($servername)
$DSTable = #();
foreach ( $db in $server.databases)
{
$dbname = $db.Name
$Srver = $db.ParentServer
foreach ( $ds in $db.Model.DataSources)
{
$hash = #
{
"Server" = $Srver;
"Model_Name" = $dbname ;
"Datasource_Name" = $ds.Name ;
"ConnectionString" = $ds.ConnectionString ;
"ImpersonationMode" = $ds.ImpersonationMode;
"Impersonation_Account" = $ds.Account;
}
$row = New-Object psobject -Property $hash
$DSTable += $row
}
}
As commented, you can surround the code you have in another foreach loop.
Using array concatenation with += is a bad idea, because on each addition, the entire array needs to be recreated in memory, so that is both time and memory consuming.
Best thing is to let PowerShell do the heavy lifting of collecting the data:
$allServers = 'server01','server02','server03' # etc. an array of servernames
# loop through the servers array and collect the utput in variable $result
$result = foreach($servername in $allServers) {
# Connect SSAS Server
$server = New-Object Microsoft.AnalysisServices.Server
$server.Connect($servername)
foreach ( $db in $server.databases) {
foreach ( $ds in $db.Model.DataSources) {
# output an object with the desired properties
[PsCustomObject]#{
Server = $db.ParentServer
Model_Name = $db.Name
Datasource_Name = $ds.Name
ConnectionString = $ds.ConnectionString
ImpersonationMode = $ds.ImpersonationMode
Impersonation_Account = $ds.Account
}
}
}
}
# output on screen
$result | Out-GridView -Title 'SSAS connection string details'
# output to a CSV file (change the path and filename here of course..)
$result | Export-Csv -Path 'D:\Test\MySSAS_Connections.csv' -UseCulture -NoTypeInformation
The above uses parameter -UseCulture because then the delimiter used for the CSV file is the same as your machine expects when double-clicking and opening in Excel. Without that, the default comma is used

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

How to properly string replace in Powershell without appending the replaced variable to a newline?

I'm pretty new to powershell/programming so bear with me. I have this bug that appends the new renamed path to a new-line without the rest of path.
The console output:
/content/pizza/en/ingredients/
helloworld/menu-eng.html
What I want:
/content/pizza/en/ingredients/helloworld/menu-eng.html
What the code below is supposed to do is rename a bunch paths. Right now testName is hard-coded but after I get this to work properly it will be dynamic.
My code:
$testName = "helloworld"
$text = (Get-Content W:\test\Rename\rename.csv) | Out-String
$listOfUri = Import-Csv W:\test\Rename\rename.csv
foreach ($element in $listOfUri) {
if ($element -match "menu-eng.html") {
$elementString = $element.'ColumnTitle' | Out-String
$elementString = $elementString.Replace('menu-eng.html', '')
$varPath1 = $elementString
$elementString = $elementString.Insert('', 'http://www.pizza.com')
$elementName = ([System.Uri]$elementString).Segments[-1]
$elementString = $elementString.Replace($elementName, '')
$elementString = $elementString.Replace('http://www.pizza.com', '')
$varPath2 = $elementString.Insert($elementString.Length, $testName + '/')
$text = $text.Replace($varPath1.Trim(), $varPath2)
}
}
$text
Assuming your .csv file looks like this:
ColumnTitle,Idk
/content/pizza/en/ingredients/SPAM/menu-eng.html,Stuff
Then:
$testName = 'helloworld'
foreach ($row in Import-CSV d:\rename.csv) {
$bit = $row.'ColumnTitle'.Split('/')[-2]
$row.'ColumnTitle'.replace($bit, $testName)
}
I have no real idea what all the rest of your code is for, particularly my earlier comment, your line:
$text = (Get-Content W:\test\Rename\rename.csv) | Out-String
is making $text into an /array/ of all the lines in the file, including the headers. You can still use .Replace() on it in PowerShell, but it's going to do the replace on every line. I can't quite see how that gives you the output you get, but it will give you multiple lines for every line in the input file.

Powershell : Extract Second line from String

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