I'm getting an automatic weekly email with an attachment, and with an outlook rule, it is being saved in a folder in my inbox. my goal is to create a PowerShell script that downloads the attachment from the email with the latest "ReceivedTime". I have managed to sort the Object in the folder by "ReceivedTime" and get the latest email in the list: "$emails[0]", I can see the name of the attachment but I cannot seem to find how to download the file itself.
the error I get is as follows: "You cannot call a method on a null-valued expression."
my code look like this:
$outlook = New-Object -ComObject outlook.application
$mapi = $outlook.GetNamespace("MAPI");
#Get Folder path
$filePath = "C:\Temp\test"
$inbox = $mapi.GetDefaultFolder(6)
$inbox.Folders | Select-Object FolderPath
$olFolderPath = "\\DanielMol#sodastream.com\Inbox\compliance"
#Get Emails Items from folder
$targetFolder = $inbox.Folders | Where-Object { $_.FolderPath -eq $olFolderPath }
#Sort Emails in folder by date
$emails = $targetFolder.Items | Sort-Object ReceivedTime -Descending
#download attachements
$emails[0].Attachments | Select-Object $_.saveasfile(($filePath))
I'm using this guide to download the attachment, but my use case is a bit different from what is described in the article: https://bronowski.it/blog/2020/09/saving-outlook-attachments-with-powershell/
Sorry if it is a very simple task, I'm new to PowerShell and just learning automation.
Thank you very much :)
First of all, I've noticed the following code:
#Sort Emails in folder by date
$emails = $targetFolder.Items | Sort-Object ReceivedTime -Descending
Instead, you need to use the Sort method of the Items class which sorts the collection of items by the specified property. The index for the collection is reset to 1 upon completion of this method. So, to get the first item use the 1 index instead of 0.
$emails[1].Attachments[1].SaveAsFile(($filePath))
Be aware, the Attachments object contains a set of Attachment objects that represent the attachments in an Outlook item. Use the Attachments property to return the Attachments collection for any Outlook item (except notes).
Related
I am trying to write a PowerShell scripts that accomplishes the following tasks with partial results :
Retrieving a list of SharePoint sites (including OneDrive sites) that contain a certain word in the url (done) ;
Using a ForEach-Object to retrieve the list of owners and users of each site (done);
Exporting these informations in a CSV file (partially done);
My problem is the number 3, I'm trying to make it with one column for the sites URLs, one column for the owner, and the 3rd column with all the users inside,
but unfortunately I'm only able to make the csv with the users list inside, here is the code that brought me to this point :
$username = "username#domain.onmicrosoft.com"
$password = "password" | ConvertTo-SecureString -asPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username,$password)
$Tenanturl = "https://domain-admin.sharepoint.com/"
# Connessione all'account Admin SharePoint
Connect-SPOService -Url $TenantUrl -Credential $credential
Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'" |
ForEach-Object {
Get-SPOUser -Site $_.Url
} |
Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
The result is a CSV file with LoginName,"UserType" in the cell A1, and the related info in the other rows.
What I am trying to accomplish :
First column for the sites URLs,
Second column for the owner of the sites,
and the 3rd column with all the users of each site inside.
I know I am missing a lot of stuff, but I'm not a developer whatsoever :),
these are some of the links I used to make this code,
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv?view=powershell-7
https://techcommunity.microsoft.com/t5/sharepoint/list-all-site-collection-admins-powershell/m-p/264135
what should I look for ? I'm looking for tips or even just little pieces of code,
thanks
I predict you'll run into woes with this structure here:
Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'" |
ForEach-Object {
Get-SPOUser -Site $_.Url
} |
Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
These pipes will make things confusing. As an Automation Consultant and Engineer for a variety of firms for five years, I would avoid over-reliance on pipes, as it makes debugging code tricky and error prone.
I would rewrite it like this, minimizing those piped statements:
$filteredSites = Get-SPOSite -IncludePersonalSite $true -Limit all -Filter "Owner -like 'WordIWantToFilter'"
"Found $($filteresSites.Counts) from previous line"
$sitesArray=#() #make empty array to hold results
ForEach($site in $filteredSites){
$sitesArray += Get-SPOUser -Site $_.Url
}
"Have $($sitesArray.Count) sites from previous line"
#good place to debug the output, btw :)
#prepare to export
"Run in the PowerShell ISE, we will have a left-over object `$sitesArray` we can use to test exporting "
$sitesArray | Select-Object -Property LoginName,UserType |
Export-Csv -Path "C:\Users\User\Desktop\SharePoint_Sites_List.csv" -NoTypeInformation
Then to use it, open this all in the PowerShell ISE and run it. You'll get some helpful logging to the console and the ISE leaves some state in time variables behind which makes it really easy to troubleshoot.
These changes will make it easier to determine where you're losing data. For instance, if you run this and see an empty file, and also have an empty $sitesArray, then your filtered in $filteredSites was too exclusive.
If you're still stuck, post an update.
I am trying to get To, From, subject, sent date from about a 100 .msg files that are saved in a folder on my local drive, possibly get all that info in a csv using powershell.
I ran this code I got from another post
Get-ChildItem "C:\--------\msgfileshere" -Filter *.msg |
ForEach-Object{
$outlook = New-Object -comobject outlook.application
$msg = $outlook.CreateItemFromTemplate($_.FullName)
$msg | Select From,to,subject,Senton,Cc|ft -AutoSize
}
I am able to get Subject and Senton date.
But no other information comes out related to Sender, to, Cc. Am I missing something here?
Edit: I was able to get Sender info which gives System.__ComObject as the output but not the actual email address
Without using a ton of ElseIf statements is it possible to select the the recipient of an email based on the file that will be attached to the email while iterating over all files in a folder?
I have started building this without the foreach get-ChildItem running over the folder where I create the email object, assign specific recipients, and choose a specific file out of the folder, but this is quite tedious and repetitive. I feel like there has to be a way to use an array of arrays or something where based on the file that the loop is on it pulls through the recipients and maybe a custom subject line.
There's tons of powershell email code out there so I won't repost that. Just not sure how to even attack this.
One option would be to have an accompanying CSV file, with rows for the filename, the recipient and the subject name etc. You could then import the CSV file using Import-Csv.
For example:
$emailList = Import-csv c:\users.csv
foreach ($line in $emailList) {
Write-Host "Sending message to $($line.emailAddress)"
Send-MailMessage -To $line.emailAddress `
-Subject $line.subject `
-Attachments $line.attachmentPath `
-Body $line.bodyText
}
and if you wanted to define the contents of the CSV in the PowerShell script, rather than using an external file, you could do something like:
$emailList = "emailAddress,subject,attachmentPath,bodyText
joe#bloggs.com,Attachment for Joe,c:\attachmentsToSend\joe.pdf,Attached is your file
Mary#bloggs.com,Mary's attachment,c:\attachmentsToSend\mary.pdf,Hello, Mary. Attached is your file
" | ConvertFrom-Csv
Put that above the foreach loop in the script
Long time lurker first time poster. I'm looking(of my own initiative) to see if there is a method by which I can check for missing files, that we would expect to receive on a daily basis, and be notified via e-mail.
Our company has what I'd call a relatively unhinged systems infrstructure, that since I arrived I've been chipping away here and there putting in some practices and process' to be more proactive with our monitoring.
Specifically in this case, we receive files via FTP from a vendor, that outlines our Sales and other data. These files go through some validation and the data is then imported into our ERP platform. However I am interested to put in a check, that raises and alert when a file has not been received, when expected.
The last part of that requirement can potentially change, I'm not sure how specific I can get when trying to raise an alert from an expected file.
I'll outline this by stating I'm a relative novice in this area, but there is really no one in my department any the wiser. So I've been looking into powershell.
I've created the following two bits of codes so far, that when executed appear to return files that have been created/last writ, within the last day. This would even be enough, to have this output sent via e-mail. I would be able to spot quickly if an expected file is not in the list.
GET-ChildItem -Path "Path I am checking" |
Where-Object {$_.LastWritetime -gt (get-Date).AddDays(-1)}
The above returns one .csv file. I guess if I get a returned file, then I know its been provided, and if the return is blank/zero, then I know I didn't get a file.
I've used the above for four seperate checks, checking other subfolders in the structure.
To outline the folder structure
\"App server"\"Region"\"Vendor"
There are then the following subfolders
Purchases
Sales
Tenders
VAT
Each of the above four folders then has
Incoming
Processed
I am running my checks on the processed folder for each of the four folder outlined above.
Maybe something like this will help you out:
Function Test-NewerFiles {
# We use parameters as it makes things easy when we need to change things
# CmdLetBinding makes sure that we can see our 'Write-Verbose' messages if we want to
[CmdLetBinding()]
Param (
[String]$Path = 'C:\Users\me\Downloads\Input_Test',
[String]$ExportFile = 'C:\Users\me\Downloads\Log_Test\Attachment.txt'
)
# We first save the date, then we don't need to do this every time again
$CompareDate = (Get-Date).AddDays(-1)
# Then we collect only the folders and check each folder for files and count them
Get-ChildItem -Path $Path -Directory -Recurse | ForEach-Object {
$Files = (Get-ChildItem -Path $_.FullName -File | Where-Object {$_.LastWritetime -gt $CompareDate} | Measure-Object).Count
# If we didn't find files the count is 0 and we report this
if ($Files -eq 0) {
Write-Verbose "No files found in folder $($_.FullName)"
Write-Output $_.FullName
}
# If we found files it's ok and we don't report it
else {
Write-Verbose "Files found in folder $($_.FullName)"
}
}
}
# If you don't want to see output you can remove the '-Verbose' switch
Test-NewerFiles -Verbose
$MyNewFiles = Test-NewerFiles
$MyNewFiles | Out-File -FilePath $ExportFile -Encoding utf8
if ($MyNewFiles) {
$MailParams = #{
To = 'Chuck.Norris#world.com'
From = 'MyScriptServer#world.com'
SmtpServer = 'SMTPServer'
}
Send-MailMessage #MailParams -Priority High -Attachments $ExportFile -Body 'We found problems: check attachment for details'
}
else {
Send-MailMessage #MailParams -Priority Low -Body 'All is ok'
}
The Verbose switch is only used to report progress. So we can see what it does when it's running. But when we use this code in production, we don't need these messages and just use Test-NewerFiles instead of Test-NewerFiles -Verbose.
I make a report for data usage on my disk, I get info from all selected property like name, path, size... behalf one filedescription, for each scanned file this property is empty. For example when you select a file in windows Explorer and you select property in general tab you can see "Type of file", here for an Excel file the type of file is "Microsoft Excel Worksheet (.xlsx)".
gci c:\file | select *
How can i get this info?
I like to avoid external programs when I can, so I would suggestion using the registry.
$ext = ".xlsx"
$desc = (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$ext")."(default)")")."(default)"
$desc
Microsoft Excel-regneark #Norwegian description
To use it with Select-Object you can modify it like this:
#You could define this inside Select-Object too, but it's a bit long so I extracted it first to clean up the code.
$extensiondesc = #{n="ExtensionDescription";e={ (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object Extension, $extensiondesc
Extension ExtensionDescription
--------- --------------------
.oxps XPS Document
.lnk Shortcut
.txt Text Document
Lets say $ext has the extension of the file.
For example -
$ext = ".bmp"
Following code will get you the description, if registered (you should add better error handling if appropriate for your scenario) -
$desc = (cmd /c assoc $ext).Split("=")[1]
$desc = (cmd /c assoc $desc).Split("=")[1]
Write-Host $desc
AFAIK, Powershell does not have any built in mechanism to get this information, and hence using cmd from powershell is the cheapest and easiest solution IMHO.
You could use the GetDetailsOf() method of the Shell.Application object:
$app = New-Object -COM 'Shell.Application'
$f = Get-Item 'C:\path\to\your\file'
$dir = $app.NameSpace($f.Directory.FullName)
$description = $dir.GetDetailsOf($dir.ParseName($f.Name), 2)
I have just improvised on Frode F's solution by adding a sorted list with unique entries to make it easier to read.
$extensiondesc = #{n="ExtensionDescription";
e={(Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object -unique Extension, $extensiondesc |Sort-Object #{e="Extension";Ascending=$true},#{e="ExtensionDescription";Ascending=$false}