PowerShell script to extract .xls file from specific Outlook folder - excel

I want to extract and save an .xls file from an email I receive daily. I have a rule set up which saves the email in an Outlook mailbox, within a specific subfolder of the Inbox.
The Outlook folder structure looks like this:
-> Inbox
--> Data (subfolder of "Inbox")
---> ToExtract (subfolder of "Data")
I need to extract the .xls file from the "ToExtract" folder.
I found a script that does most of the work for me, but it requires the user to supervise the script and manually select which Outlook folder to search. I need to change the script so it just points to the "ToExtract" subfolder.
The code is below. It works fine, but I need to modify the pickfolder() part.
#file path
$filepath = “c:\test\”
#set outlook to open
$o = New-Object -comobject outlook.application
$n = $o.GetNamespace(“MAPI”)
#you'll get a popup in outlook at this point where you pick the folder you want to scan
$f = $n.pickfolder()
#date string to search for in attachment name
$date = Get-Date -Format yyyyMMdd
#now loop through them and grab the attachments
$f.Items | foreach {
$_.attachments | foreach {
Write-Host $_.filename
$a = $_.filename
If ($a.Contains($date)) {
$_.saveasfile((Join-Path $filepath $a))
}
}
}

Give this a shot.
$Account = $n.Folders | ? { $_.Name -eq 'username#domain.com' };
$Inbox = $Account.Folders | ? { $_.Name -match 'Inbox' };
$f = $Inbox.Folders | ? { $_.Name -match 'ToExtract' };

$MailboxName = "MAILBOX"
$Subject = "EMAIL SUBJECT"
$ProcessedFolderPath = "/Inbox/Processed"
$downloadDirectory = "c:\temp"
Function FindTargetFolder($FolderPath){
$tfTargetidRoot = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$tfTargetidRoot)
$pfArray = $FolderPath.Split("/")
for ($lint = 1; $lint -lt $pfArray.Length; $lint++) {
$pfArray[$lint]
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+isEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$pfArray[$lint])
$findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
if ($findFolderResults.TotalCount -gt 0){
foreach($folder in $findFolderResults.Folders){
$tfTargetFolder = $folder
}
}
else{
"Error Folder Not Found"
$tfTargetFolder = $null
break
}
}
$Global:findFolder = $tfTargetFolder
}
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
$windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$sidbind = "LDAP://<SID=" + $windowsIdentity.user.Value.ToString() + ">"
$aceuser = [ADSI]$sidbind
$service.AutodiscoverUrl($aceuser.mail.ToString())
FindTargetFolder($ProcessedFolderPath)
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
$Sfir = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead, $false)
$Sfsub = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject, $Subject)
$Sfha = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::HasAttachments, $true)
$sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
$sfCollection.add($Sfir)
$sfCollection.add($Sfsub)
$sfCollection.add($Sfha)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000)
$frFolderResult = $InboxFolder.FindItems($sfCollection,$view)
foreach ($miMailItems in $frFolderResult.Items){
$miMailItems.Subject
$miMailItems.Load()
foreach($attach in $miMailItems.Attachments){
$attach.Load()
$fiFile = new-object System.IO.FileStream(($downloadDirectory + “\” + $attach.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attach.Content, 0, $attach.Content.Length)
$fiFile.Close()
write-host "Downloaded Attachment : " + (($downloadDirectory + “\” + $attach.Name.ToString()))
}
$miMailItems.isread = $true
$miMailItems.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
[VOID]$miMailItems.Move($Global:findFolder.Id)
}
From https://gist.github.com/bleep-io/5151579

Related

Running a powershell script using multiple threads

I'm trying to execute a powershell script from within another powershell script. My current script runs fine, but I'd like to try and speed it up if possible.
What my script does is import a list of contacts into Each users Contacts folder through EWS.
In my powershell script the script that handles the importing I call it like this (ImportContacts is a Function without any arguments):
. $rootPath\ImportContacts\ImportContacts.ps1
ImportContacts
When I run it normally, as I mentioned above everything works fine, I just would like to speed it up. I tried following some examples of implementing runspacepool in Powershell to take advantage of using multiple threads, but it doesn't seem to be working properly for me. I'm sure it's a silly syntax error, but I have this currently:
Measure-Command{
$MaxThreads = 5
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads)
$RunspacePool.Open()
$Jobs = #()
ImportContacts | Foreach-Object {
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$PowerShell.AddScript({ImportContacts})
$Jobs += $PowerShell.BeginInvoke()
}
while ($Jobs.IsCompleted -contains $false)
{
Start-Sleep 1
}}
This seems to do the job, but I can't tell a difference with the speed.
EDIT 5-15-21
To better assist with the question, here is how I retrieve the user data. I have a file I call called "ExportedContacts.ps1"
It's called like so:
$Users = & $rootPath\ExportContacts\ExportContacts.ps1
The contents of the file is this
$Users = Get-ADUser -Filter * -Properties extensionAttribute2, middlename, mobile, OfficePhone, GivenName, Surname, DisplayName, EmailAddress, Title, Company, Department, thumbnailPhoto | Where-Object {($_.extensionAttribute2 -like "DynamicDistro") -AND (($_.Mobile -ne $NULL) -OR ($_.OfficePhone -ne $NULL))} | Select-Object #{Name="First Name";Expression={$_.GivenName}},#{Name="Last Name";Expression={$_.Surname}},#{Name="Display Name";Expression={$_.DisplayName}},#{Name="Job Title";Expression={$_.Title}},#{Name="Company";Expression={$_.Company}},#{Name="Department";Expression={$_.Department}},#{Name="Mobile Phone";Expression={$_.Mobile}},#{Name="Home Phone";Expression={$_.OfficePhone}}, #{Name="Middle Name";Expression={$_.MiddleName}}, #{Name="E-mail Address";Expression={$_.EmailAddress}}, thumbnailPhoto | Sort-Object "Last Name"
return $Users
Then I import those contacts, similarly to how I mentioned above. The content of the import is as follows:
Function ImportContacts
{
Write-Host "Importing Contacts. This can take several minutes."
#******************************************************************
foreach ($ContactItem in $Users)
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
$ExchangeContact.NickName = ('{0} {1}' -f $ContactItem."First Name", $ContactItem."Last Name"). Trim()
$ExchangeContact.DisplayName = $ExchangeContact.NickName;
$ExchangeContact.FileAs = $ExchangeContact.NickName;
$ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1] = $ContactItem."E-mail Address";
$ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone] = $ContactItem."Home Phone";
$ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = $ContactItem."Mobile Phone";
$ExchangeContact.Department = $ContactItem."Department";
$ExchangeContact.CompanyName = $ContactItem."Company";
$ExchangeContact.JobTitle = $ContactItem."Job Title";
$ExchangeContact.MiddleName = $ContactItem."Middle Name";
# Save the contact
$ExchangeContact.Save($ContactsFolder.Id);
}
}
I am also including the File that creates the contacts folder I specify and also deletes existing contacts (if the folder already exists), so that each import is a clean and updated import. I'd be curious if there is a faster way to clean the existing items?
Function CreateContactsFolder
{
Write-Host "Creating Contacts Folder and Cleaning up stale items. This can take a couple minutes."
Try
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()
#Check to see if they have a contacts folder that we want
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where-Object {$_.DisplayName -eq $FolderName}
if($ContactsFolderSearch)
{
$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
#If folder exists, connect to it. Clear existing Contacts, and reupload new (UPDATED) Contact Info
Write-Host "Folder alreads exists. We will remove all contacts under this folder."
# Attempt to empty the target folder up to 10 times.
$tries = 0
$max_tries = 0
while ($tries -lt 2)
{
try
{
$tries++
$ErrorActionPreference='Stop'
$ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
$tries++
}
catch
{
$ErrorActionPreference='SilentlyContinue'
$rnd = Get-Random -Minimum 1 -Maximum 10
Start-Sleep -Seconds $rnd
$tries = $tries - 1
$max_tries++
if ($max_tries -gt 100)
{
Write-Host "Error; Cannot empty the target folder; `t$EmailAddress"
}
}
}
}
else
{
#Contact Folder doesn't exist. Let's create it
try
{
Write-Host "Creating new Contacts Folder called $FolderName"
$ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
$ContactsFolder.DisplayName = $FolderName
$ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
}
catch
{
Write-Host "Error; Cannot create the target folder; `t$EmailAddress"
}
}
return $ContactsFolder
}
Catch
{
Write-Host "Couldn't connect to the user's mailbox. Make sure the admin account you're using to connect to has App Impersonization permissions"
Write-Host "Check this link for more info: https://help.bittitan.com/hc/en-us/articles/115008098447-The-account-does-not-have-permission-to-impersonate-the-requested-user"
}
}

Powershell Error: Exception from HRESULT: 0x800A03EC

Any help for this would be great. running into this error and i am unable to figure out why its failing to write to row 4. I have tried different formats and still continues to throw the error. i have tried this in xls and csv. no luck, again any help would be great thank you!
$path = ".\results.csv"
$objExcel = new-object -comobject excel.application
if (Test-Path $path) {
$objWorkbook = $objExcel.WorkBooks.Open($path)
$objWorksheet = $objWorkbook.Worksheets.Item(1)
} else {
$objWorkbook = $objExcel.Workbooks.Add()
$objWorksheet = $objWorkbook.Worksheets.Item(1)
}
$objExcel.Visible = $True
#########Add Header#########
$objWorksheet.Cells.Item(1, 1) = "MachineIP"
$objWorksheet.Cells.Item(1, 2) = "Result"
$objWorksheet.Cells.Item(1, 3) = "HostName"
$objWorksheet.Cells.Item(1, 4) = "ServiceTag"
$ipadd = Read-Host "Please enter the IP address ex. 10.0.0. "
75..190 | ForEach-Object {$ipadd + "$_"} | Out-File -FilePath .\machinelist.txt
Start-Sleep -s 3
$machines = gc .\machinelist.txt
$count = $machines.count
$row=2
$machines | foreach-object{
$ping=$null
$hname =$null
$machine = $_
$ping = Test-Connection $machine -Quiet -Count 1 -ea silentlycontinue
if($ping){
try{
$hname = [System.Net.Dns]::GetHostByAddress($machine).HostName
}catch{}
try{
$stag = Get-WmiObject -ComputerName $machine Win32_BIOS | Select-Object SerialNumber
}catch{}
$objWorksheet.Cells.Item($row,1) = $machine
$objWorksheet.Cells.Item($row,2) = "UP"
$objWorksheet.Cells.Item($row,3) = $hname
$objWorksheet.Cells.Item($row,4) = $stag
$row++
} else {
}
}
Remove-Item -Path .\machinelist.txt -Force
When I ran your code, I saw two issues that kept popping up.
The RPC Server is unavailable (for IP address that didnt resolve)
Unable to assign value to the cell.
For 1, I simply added -ErrorAction SilentlyContinue and for 2, i added quotes around the value for $stag. See script
if($ping){
try{
$hname = [System.Net.Dns]::GetHostByAddress($machine).HostName
}catch{}
try{
$stag = Get-WmiObject -ComputerName $machine Win32_BIOS -ErrorAction SilentlyContinue | Select-Object SerialNumber
$objWorksheet.Cells.Item($row,1) = "$machine"
$objWorksheet.Cells.Item($row,2) = "UP"
$objWorksheet.Cells.Item($row,3) = "$hname"
$objWorksheet.Cells.Item($row,4) = "$stag"
}
catch{
$objWorksheet.Cells.Item($row,1) = "$machine"
$objWorksheet.Cells.Item($row,2) = "DOWN"
$objWorksheet.Cells.Item($row,3) = "$hname"
$objWorksheet.Cells.Item($row,4) = "$stag"
}
$row++
}
If the post helped you get to your solution, please mark the post answered.

Switching Lastname, Firstname in Powershell AD script

I try to export AD groups and users from a OU by Firstname, Lastname but I only get it to work with Lastname, Firstname.
Everything else I try gives me an empty string for members.I tried changing the line under Select-Object to:
#{Name='Member';Expression={$_.FirstName = GetFirstName $_.Name $_.LastName = GetLastName $_.Name}},
$firt = $_.firstname
$last = $_.lastname
#{Name='Member';Expression={$_.name = "$first,$last"}},
This is the working code, but the names should be switched around.
$OU = 'OU=Groups,OU=City,OU=Continent,DC=DomainControler, DC=Domain, DC=net' #Change this to get different groups
$DateTime = Get-Date -f "dd-MM-yyyy"
$MyFileName = "CompanyName-Groups_"+$DateTime+".csv"
$Path = Join-Path $PSScriptRoot $MyFileName
$Groups = get-adobject -Filter 'ObjectClass -eq "group"' -SearchBase $OU
$i=0
$tot = $Groups.count
$Data = foreach ($Group in $Groups) {
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot * 100)
Get-ADGroupMember -Identity $Group |
Select-Object #{Name='Group';Expression={$Group.Name}},
#{Name='Member';Expression={$_.Name}},
#{Name='Enabled';Expression={if ($_.ObjectClass -eq 'user') {Get-ADUser $_ | Select-Object -Expand Enabled} else {'NA/Group'}}}
}
$Data | Export-Csv -Path $Path -NoTypeInformation
This is an example output:
Group, "member", enabled
Admin, "Mario, Speedwagon", True
Admin, "Petey, Cruiser", True
Admin, "Anna, Sthesia", False
HR, "Paul, Molive", True
HR, "Phaedra, Lugt", True
IT, "Paul, Molive", False
IT, "Cliff, Hanger", True
This is what it should become:
Group, "member", enabled
Admin, "Speedwagon, Mario", True
Admin, "Cruiser, Petey", True
Admin, "Sthesia, Anna", False
HR, "Molive, Paul", True
HR, "Lugt, Phaedra", True
IT, "Molive, Paul", False
IT, "Hanger, Cliff", True
I think this might clear things up for you:
$OU = 'OU=Groups,OU=City,OU=Continent,DC=DomainControler, DC=Domain, DC=net'
$PathParams = #{
Path = $PSScriptRoot
ChildPath = "PA-AD-Groups_{0}.csv" -f (Get-Date -f "dd-MM-yyyy")
}
$FilePath = Join-Path #PathParams
$Groups = Get-ADObject -Filter 'ObjectClass -eq "group"' -SearchBase $OU
$i = 0
$tot = $Groups.count
$Data = foreach ($Group in $Groups) {
$i++
$ProgressParams = #{
Activity = 'Exporting AD Groups'
PercentComplete = ($i / $tot * 100)
status = "Processing Group $i of $tot : {0:N0} Completed" -f
($i / $tot * 100)
}
Write-Progress #ProgressParams
Get-ADGroupMember -Identity $Group |
Select-Object #{Name = 'Group'; Expression = {$Group.Name}},
#{Name = 'Member'; Expression = {$_.Name}},
#{Name = 'Enabled'; Expression = {
$Script:User = $false
if ($_.ObjectClass -eq 'user') {
$Script:User = Get-ADUser $_
if ($User.Enabled) {$true} else {$false}
}
else {
'NA/Group'
}
}
},
#{Name = 'FirstName'; Expression = {
if ($User) {
$User.GivenName
}
}
},
#{Name = 'LastName'; Expression = {
if ($User) {
$User.Surname
}
}
},
#{Name = 'CombinedName'; Expression = {
if ($User) {
"{0}, {1}" -f $User.GivenName, $User.Surname
}
}
}
}
$Data | Export-Csv -Path $FilePath -NoTypeInformation
The issue you have is that you can't use the properties from $User outside of the Expression within the Select-Object. This is simply fixed by creating a variable that is available throughout the script and as such is called Script scope, used as $Script:User.
More info can be found in Get-Help about_Scopes or here.
On a side note I would advice you to use proper indentation, it makes things more readable. The splatted parameter hashtable helps in this regard too. As a last tip: don't create a variable if you only use it once. Otherwise it just confuses you later on.

migrate document with its meta data from one document library to a folder

I am beginner in sharepoint specially in powershell.
I am trying to move all the documents of a document library from one site to another site inside a folder. but I could just copy the files not metadata whcih come from contenttypes!
$PSSnapin = Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue Out-Null
clear
$org = "http://'YYYYY'/sites/XXXX"
$dest = "http://'YYYYY'/sites/XXXX/Subsite"
$orgLibrary = (Get-SPWeb $org).Folders["newdoc1"]
$destLibrary = (Get-SPWeb $dest).Folders["newdoc2"].SubFolders["folder1"]
$destFiles = $destLibrary.Files
foreach ($file in $orgLibrary.Files)
{
$curFile = $file.OpenBinary()
$destURL = $destFiles.Folder.Url + "/" + $file.Name
$destFiles.Add($destURL, $curFile, $true)
}
This one here does just that. Except for modified and created dates, all other fields are brought over.
$web = Get-SPWeb "http://sharepointed.com/"
$list = $web.Lists["Shared Documents"]
$spQuery = New-Object Microsoft.SharePoint.SPQuery
$spQuery.ViewAttributes = "Scope='Recursive'";
$spQuery.RowLimit = 2000
$caml = '<Where><Lt><FieldRef Name="Created" /><Value IncludeTimeValue="TRUE" Type="DateTime">2014-01-01T04:06:45Z</Value></Lt></Where> '
$spQuery.Query = $caml
do
{
$listItems = $list.GetItems($spQuery)
$spQuery.ListItemCollectionPosition = $listItems.ListItemCollectionPosition
$listTotal = $listItems.Count
for ($x=$listTotal-1;$x -ge 0; $x--)
{
try
{
$listItems[$x].CopyTo("http://sharepoint/Docs/Documents/"+ $listItems[$x].name)
Write-Host("DELETED: " + $listItems[$x].name)
$listItems[$x].Recycle()
}
catch
{
Write-Host $_.Exception.ToString()
}
}
}
while ($spQuery.ListItemCollectionPosition -ne $null)
via Ian

How to update SharePoint enterprise keywords with multiple values using powershell?

I'm trying to write a powershell script that looks at a user defined variable and updates the enterprise keywords for each item in a list that matches that value.
For instance, say you have a page in SP that has the managed metadata keywords: new, fresh, clean
I want a script that asks a user what keyword they want to swap out. So a user would specify a variable as: fresh and another variable as: fresher and it would update any item with the keyword fresh to fresher.
Here's what I 've used before but doesn't work now because there are mutliple values:
Add-PSSnapin Microsoft.SharePoint.PowerShell -EA SilentlyContinue
$webURL = <MY SP URL>
$listName = <MY LIST NAME>
$web = Get-SPWeb $webURL
$list = $web.Lists[$listName]
$listitems = $list.items
$session = Get-SPTaxonomySession -Site $web.Site
$termStore = $session.TermStores["Managed Metadata Service"]
$group = $termStore.Groups["Resources"]
$termset = $group.TermSets["Wiki Categories"]
$terms = $termSet.GetTerms(100)
$wiki1 = read-host "Enter the wiki category you want to update"
$wiki2 = read-host "Enter the replacement wiki category"
$term = $terms | ?{$_.name -eq $wiki2}
Foreach($item in $listitems)
{$wiki = $item["Wiki Categories"]
if($wiki.label -eq $term)
{
$spitem = [Microsoft.SharePoint.SPListItem]$item;
$taxfield = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$spitem.Fields["Wiki Categories"]
$taxfield.SetFieldValue($spitem, $term)
$spitem.Update()
$spitem.File.Publish("True")
}
}
I'm pretty sure the issue is with this line:
$term = $terms | ?{$_.name -eq $wiki2}
And this line:
$taxfield.SetFieldValue($spitem, $term)
The problem is that you passing a TermCollection ($term) to SetFieldValue where you should pass in a TaxonomyFieldValueCollection.
You can convert them like this:
$taxfield = $spitem.Fields["Wiki Categories"]
$tfvc = new-object -typename Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection -argumentlist $taxfield;
foreach($t in $ term)
{
$tfv = new-object -typename Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue -argumentlist $taxfield
$tfv.TermGuid = $t.Id
$tfv.Label = $t.Name
$tfvc.Add($tfv)
}
...
$taxfield.SetFieldValue($spitem, $tfvc)

Resources