How to target outlook subfolder using powershell - powershell

I'm trying to filter about 2000 automated alerts in an outlook sub-folder.
I need to do the following series of steps:
Parse sub-folder Account Alert Lockouts
Search for a specific phrase that has a variable username
Dump out that whole phrase with the variable username into csv
Example Phrase
Account Name: jdoe
I have all of the required emails in a sub-folder, I just need to analyze them.
I've gotten my code to work in the Inbox, but it doesn't cover the sub-folder.
Add-Type -Assembly "Microsoft.Office.Interop.Outlook"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$RE = [RegEx]'(?sm)Account Name\s*:\s*(?<AccName>.*?)$.*'
$DebugPreference = 'Continue'
$Data = foreach ($item in $inbox.items) {
if ($item.body -match $RE) {
Write-Host "ding "
[PSCustomObject]#{ AccName = $Matches.AccName }
}
}
$Data
$Data | Export-CSv '.\data.csv' -NoTypeInformation

Per the documentation for NameSpace.GetDefaultFolder:
To return a specific non-default folder, use the Folders collection.
And the documentation for the Folders collection referenced above:
Use Folders (index), where index is the name or index number, to return a single Folder object. Folder names are case-sensitive.
You should be able to add this:
$subfolder = $inbox.Folders('Account Alert Lockouts')
and change your foreach to iterate over $subfolder.

Related

How to change script to save attachments by file name instead of by sender name?

I'm using the script below. It works well to save attachments by sender name in a specified folder. However, if the sender names are constant, it only saves 1 of the attachments vs. all of the attachments. I'm assuming it's a write error. How do I update the script below to save all attachments meeting the filtered criteria by their actual attachment name instead of sender name.
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace("MAPI")
$f = $n.PickFolder()
$filepath = "c:\test"
$f.Items| foreach {$SendName = $_.Sendername
$_.attachments|foreach {
$_.filename
If ($_.filename.Contains("pdf")) {
$_.saveasfile((Join-Path $filepath "$SendName.pdf"))}}}`
Any ideas would be greatly appreciated.
So Lets Follow the rabbit hole here.
We can go to the Outlook Object Model, and look for Attachments Object because we see you are iterating over attachments:
$_.attachments|foreach
We see in the page:
Contains a set of Attachment objects
So we go look at the Attachment Object Page, look at Properties and we see there is a property for FileName
So to send by attachment name we can do this:
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace("MAPI")
$f = $n.PickFolder()
$filepath = "c:\test"
$f.Items| foreach {
$FileName= $_.FileName
$_.attachments|foreach {
$_.filename
If ($_.filename.Contains("pdf")) {
$_.saveasfile((Join-Path $filepath "$FileName"))
}
}
}

Getting the Oldest Item in Outlook via Powershell

I'm working on something that is extracting information from my desktop Outlook application. It works for most of the folders I've tried it on, but for some that have nearly a decade of e-mails, I get a "Exception getting 'ReceivedTime': 'Insufficient memory to continue the execution of the program." This is what I'm trying:
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
# Folder/Inbox
$folder = $mapi.Folders.Item('name#email.com').Folders.Item('Inbox')
# Sort by the Received Time
$contents = $folder.Items | sort ReceivedTime
# Get the first element in the array, convert to JSON, and then output to file
echo $contents[0] | convertTo-Json | Out-File C:\Users\ME\outlook_1.json -Encoding UTF8
Is there a better way of approaching this? I'm on Powershell 5.1.
EDIT: I've also tried this, which is looping through the array and then breaking on the first instance, but received the same error:
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
# Folder/Inbox
$folder = $mapi.Folders.Item('name#email.com').Folders.Item('Inbox')
# Sort by the Received Time
$contents = $folder.Items | sort ReceivedTime
$i = 1
foreach($item in $contents){
if (-not ([string]::IsNullOrEmpty($item))){
echo $item | convertTo-Json | Out-File Out-File C:\Users\ME\outlook_1.json -Encoding UTF8-Encoding UTF8
Break
}
}
Sort the items collection using Items.Sort("ReceivedTime", false), then read the first item using Items(1).
Make sure you store Items collection in a variable instead of accessing MAPIFolder.Items multiple times, otherwise you will get a brand new Items object every time you do that.
EDIT: I'm the OP of the question and am putting the correct code here for those who might be as dense as I am and not initially realize what is being said!
# New Outlook object
$ol = new-object -comobject "Outlook.Application";
# MAPI namespace
$mapi = $ol.getnamespace("mapi");
$folder = $mapi.Folders.Item('name#gmail.com').Folders.Item('Inbox')
# Get the items in the folder
$contents = $folder.Items
# Sort the items in the folder by the metadata, in this case ReceivedTime
$contents.Sort("ReceivedTime")
# Get the first item in the sorting; in this case, you will get the oldest item in your inbox.
$item = $contents.GetFirst()
echo $item
# If instead, you wanted to get the newest item, you could do the same thing but do $item = $contents.GetLast()

Save Email body to html file powershell

I am trying to convert a folder full of MSG files to HTML files. I have a scrip that gets most of the way there, but instead of displaying the text in powershell I need it to save each one as an individual html file. For some reason I can't get the save to work. I've tried various options like out-file and $body.SaveAs([ref][system.object]$name, [ref]$saveFormat)
$saveFormat = [Microsoft.Office.Interop.Outlook.olSaveAsType]::olFormatHTML
Get-ChildItem "C:\MSG\" -Filter *.msg |
ForEach-Object {
$body = ""
$outlook = New-Object -comobject outlook.application
$msg = $outlook.Session.OpenSharedItem($_.FullName)
$body = $msg | Select body | ft -AutoSize
}
Any advice on how to save this as individual files would be great.
To start with, you should not capture the output of a Format-* cmdlet in a variable. Those are designed to output to something (screen, file, etc).
Ok, that aside, you are already opening the msg files, so you just need to determine a name and then output the HTMLBody property for each file. Easiest way would be to just tack .htm to the end of the existing name.
Get-ChildItem "C:\MSG\*" -Filter *.msg |
ForEach-Object {
$body = ""
$outlook = New-Object -comobject outlook.application
$msg = $outlook.Session.OpenSharedItem($_.FullName)
$OutputFile = $_.FullName+'.htm'
$msg.HTMLBody | Set-Content $OutputFile
}

Copy Sheets from Existing to new Workbook and use a cell as a reference for file name

Am trying to automate certain tasks that I have to do, that although simple are tedious, due to the number of files. I currently have a script that will refresh every file within a folder, now these files have more worksheets that what my client needs, so after refreshing, I need to copy/paste the first two sheets in a new workbook, save in a general location where the client pick's it up. I have added what I thought was good code to do this copy/paste, but unfortunately, I'm getting errors in the copy/paste section as well as the SaveAs part. I did some research here and at "powershell.org", but couldn't find anything that helped :(.
This is my code:
Measure-Command {
$excel = new-object -comobject excel.application
$excel.DisplayAlerts = $false
$excelFiles = Get-ChildItem -Path "Network folder location" -Include *.xls, *.xlsm,*.xlsx, *.lnk -Recurse
Foreach($file in $excelFiles) {
$workbook = $excel.workbooks.open($file.fullname)
foreach ($Conn in $workbook.Connections){
$Conn.OLEDBConnection.BackgroundQuery = $false
$Conn.refresh()
}
$workBook.RefreshAll()
$workbook.save()
$wb2 = $excel.Workbooks.Add()
$sheetToCopy = $workbook.sheets.item(1),$workbook.sheets.item(2) #Source
$sheetToCopy.CopyTo($wb2) #Destination
$filename = $wb2.Sheets.Item(2).Cells.Item(4,2) #Destination file, 2nd sheet, column D row 2 has what I want to call the file (RVP John Doe - Dashboard)
$wb2.SavesAs("Networkfolder\$filename.xlsx")
$workbook.close()
$wb2.close()
}
$excel.quit()
}

powershell set category for multiple files and subdirectories

I've been putting "tags" into the names of files, but that's a terrible way of organizing a large number of files.
ex: "ABC - file name.docx"
So, I want to set the category attribute to "ABC" instead of having it in the name using PowerShell. The script would have to find all of the files with "ABC" in its name in the subdirectories of a certain folder and set the category attribute to "ABC".
So I have the first part where I am finding the files but I don't know where to go from here.
Get-ChildItem -Filter "ABC*" -Recurse
Any ideas?
Thanks.
So this borrow heavily from the Scripting Guys. What we need to do is for every file we find use the a Word COM object to access those special properties of the file. Using the current file name we extract the "category" by splitting on the first hyphen and saving both parts. First becomes the category and second is the new name we give the file assuming the category update was successful.
There is still margin for error with this but this
$path = "C:\temp"
# Create the Word com object and hide it sight
$wordApplication = New-Object -ComObject word.application
$wordApplication.Visible = $false
# Typing options for located Word Documents. Mainly to prevent changes to timestamps
$binding = "System.Reflection.BindingFlags" -as [type]
# Locate Documents.
$docs = Get-childitem -path $Path -Recurse -Filter "*-*.docx"
$docs | ForEach-Object{
$currentDocumentPath = $_.fullname
$document = $wordApplication.documents.open($currentDocumentPath)
$BuiltinProperties = $document.BuiltInDocumentProperties
$builtinPropertiesType = $builtinProperties.GetType()
$categoryUpdated = $false # Assume false as a reset of the state.
# Get the category from the file name for this particular file.
$filenamesplit = $_.Name.split("-",2)
$category = $filenamesplit[0].Trim()
# Attempt to change the property.
Try{
$BuiltInProperty = $builtinPropertiesType.invokemember("item",$binding::GetProperty,$null,$BuiltinProperties,"Category")
$BuiltInPropertyType = $BuiltInProperty.GetType()
$BuiltInPropertyType.invokemember("value",$binding::SetProperty,$null,$BuiltInProperty,[array]$category)
$categoryUpdated = $true
}Catch [system.exception]{
# Error getting property so most likely is not populated.
Write-Host -ForegroundColor Red "Unable to set the 'Category' for '$currentDocumentPath'"
}
# Close the document. It should save by default.
$document.close()
# Release COM objects to ensure process is terminated and document closed.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($BuiltinProperties) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
Remove-Variable -Name document, BuiltinProperties
# Assuming the category was successfully changed lets remove the information from the current filename as it is redundant.
If($categoryUpdated){Rename-Item $currentDocumentPath -NewName $filenamesplit[1].Trim()}
}
$wordApplication.quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($wordApplication) | Out-Null
Remove-Variable -Name wordApplication
[gc]::collect()
[gc]::WaitForPendingFinalizers()
You should see some explanation in comments that I tried to add for clarification. Also read the link above to get more of an explanation as to what is happening with the COM object.