Powershell error during making copy of outlook folder - vba

I try to make copy of all folders from user account into pst file. Script works fine for all folders except Inbox. When I try to copy data from inbox at end of copying I have error
Exception calling "CopyTo" with "1" argument(s): "Cannot move or copy folders. Cannot copy folder. A top-level folder cannot be copied to one of its subfolders. Or, you may not have appropriate permissions for the folder. To check your permissions for the folder, right-click the folder, and then click Properties on the shortcut menu."
Script below
$start = Get-Date
#checking if outlook is running
$isRunning = (#(Get-Process -ea silentlycontinue OUTLOOK).count -gt 0)
#showing dialog to choose direcotry where pst should be saved
$app = new-object -com Shell.Application
$folder = $app.BrowseForFolder(0, "Select Folder", 0, "C:\")
if ($folder.Self.Path -ne "") {
$path = $folder.Self.Path+"\"+$env:USERNAME+".pst"
} else {
Write-Host "directory not selected. Exiting.."
[Environment]::Exit(1)
}
#reference to outlook
Add-Type -Assembly "Microsoft.Office.Interop.Outlook" | out-null
$outlook = New-Object -ComObject outlook.application
$ns = $outlook.GetNamespace("MAPI")
#adding personal archive file
$ns.AddStoreEx($path, 1)
Write-Host created $path
$dst = $ns.Folders.GetLast()
Write-Host folder name: $dst.Name
#list of directory types to copy. more details https://msdn.microsoft.com/en-us/library/office/ff861868.aspx
$folderTypes = 9,10,3,16,6,12,4,5,30,13,28
#iterating trough list of directories and for each dir make copy in pst file. next counting of objects in archive and pst
foreach ($id in $folderTypes) {
$src = $ns.GetDefaultFolder($id)
$tmp = $src.copyTo($dst)
Write-Host copied to $tmp.name
}
#deattaching personal store from outlook
$ns.GetType().InvokeMember('RemoveStore',[System.Reflection.BindingFlags]::InvokeMethod, $null, $ns, ($dst))
Write-Host $dst.name closed
#if outlook wasn't running on start we must close it
if (-not $isRunning) {
Write-Host closing outlook
$outlook.Quit()
}
$end = Get-Date
$diff = New-TimeSpan -start $start -end $end
Write-Host done
"time elapsed {0:g}" -f $diff
Any ideas why this error occurs?
Thanks in advace

Make sure that you don't have the DisableCrossAccountCopy key in the windows registry set. Here is what the CopyTo method description states:
Setting the REG_MULTI_SZ value, DisableCrossAccountCopy, in HKCU\Software\Microsoft\Office\14.0\Outlook in the Windows registry has the side effect of disabling this method.
Also I'd suggest releasing underlying COM objects in the code instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. This is particularly important if your add-in attempts to enumerate more than 256 Outlook items in a collection that is stored on a Microsoft Exchange Server. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. Read more about that in the Systematically Releasing Objects article.

Related

Programmatically exporting .PBIX files to .PDF

I'm struggling to use the Power BI API to programmatically download multiple reports into a pdf file format. I've written a short powershell script that connects to my accounts and produces a list of all available reports then exports the list as a csv. As there is no 'Export-pdf' attribute is there a work around? Here is my script below.
{
Connect-PowerBIServiceAccount
$myWorkspaces = Get-PowerBIWorkspace
Write-Host “The current user has –” $myWorkspaces.Count “– workspaces.”
$Reports =
ForEach ($workspace in $Workspaces)
{
Write-Host $workspace.Name
ForEach ($report in (Get-PowerBIReport -Scope Individual -WorkspaceId $workspace.Id))
{
[pscustomobject]#{
WorkspaceID = $workspace.Id
WorkspaceName = $workspace.Name
ReportID = $report.Id
ReportName = $report.Name
ReportURL = $report.WebUrl
ReportDatasetID = $report.DatasetId
}
}
}
Write-Host “The current user has –” $Reports.Count “– reports.”
$Reports | Export-csv -Path C:\Users\jackbolshaw\Desktop\eTech\Test\report.csv -NoTypeInformation
Disconnect-PowerBIServiceAccount
}

Powershell Excel - Using new range for search gives errors for old range

I am a complete beginner to Microsoft PowerShell, but I have worked with C++, Java, and C#. I was working on this small script for my job, and I came across a strange issue that is likely due to me not properly understanding how ranges work. This script is supposed to open up an Excel workbook, search each sheet for a name, and give the names that match along with their information. The problem is when I set the range again to search for the starting column index of information (in this case, the column to the right of the column labeled "Description"), it breaks the range when it searches for more than one match of the same last name.
I have a do-while loop that uses worksheet.range.entirerow.findnext() so that I can find multiple with the same last name. This works until I used a new range, worksheet.range.entirecolumn.find(). This is my latest code of what I tried, but I have already tried hardcoding $Range to 5 (which worked, but I want it to be dynamic) or used a new variable $RowRange (which didn't fix the issue). If I understood correctly, the range is like the current selection of two or more cells, so why can I not reset it or use a new variable? It does not loop, so I only keep finding the first name in each sheet.
P.S. As a side question, I had an issue of shutting down the process of this excel document I want to open in the background without shutting down other Excel workbooks. For some reason, using Get-Process EXCEL | Stop-Process -Force; shuts down ALL of my open workbooks. I commented it out, but I'm worried about the process not quite ending when it's done executing this code.
# Prepare output file for results
$FileName = "TEST";
$OutputFile = "Results.txt";
Remove-Item $OutputFile;
New-Item $OutputFile -ItemType file;
$Writer = [System.IO.StreamWriter] $OutputFile;
Clear-Host
Write-Host Starting...
# Start up Excel
$Excel = New-Object -ComObject Excel.Application;
$File = $FileName + ".xlsx";
# Prompt user for last name of person to search for (and write to the Results.txt output file)
Clear-Host
Write-Host Search for users in each region by their last name.
$SearchLastName = Read-Host -Prompt "Please input the person's last name";
Write-Host Searching for person...;
$Writer.WriteLine("Name Search: " + $SearchLastName);
$Writer.WriteLine("");
# Then open it without it being made visible to the user
$Excel.Visible = $false;
$Excel.DisplayAlerts = $true;
$Excel.Workbooks.Open($File);
# For each worksheet, or tab, search for the name in the first column (last names)
$Excel.Worksheets | ForEach-Object{
$_.activate();
$Range = $_.Range("A1").EntireColumn;
# Note: To search for text in the ENTIRETY of a cell, need to use the find method's lookat
# parameter (use 1). Otherwise, if searching for Smith, Nesmith also gets detected.
$SearchLast = $Range.find($SearchLastName,[Type]::Missing,[Type]::Missing,1);
$Writer.WriteLine($_.name + ": ");
if ($SearchLast -ne $null) {
$FirstRow = $SearchLast.Row;
do {
# If a first name was found, get the first name too
$FirstName = $_.Cells.Item($SearchLast.Row, $SearchLast.Column + 1).value();
# Then display in proper order
$Writer.WriteLine(" " + $SearchLast.value() + "," + $FirstName);
# From here, find the relevant information on that person
# Search for the column labeled "Description", the starting column is the next one, ending column is the number of used columns
$BeginCol = $_.Range("A1").EntireRow.find("Description",[Type]::Missing,[Type]::Missing,1).Column + 1;
$MaxColumns = $_.UsedRange.Columns.Count;
# Check each column for relevant information. If there are no extra rows after "Description" just skip
for ($i = $BeginCol; $i -le $MaxColumns; $i++) {
# The information of the current cell, found by the row of the name and the current row
$CurrentCell = $_.Cells.Item($SearchLast.Row, $i);
# Only add the description if it exists.
if (!([string]::IsNullOrEmpty($CurrentCell.value2))) {
$Description = $_.Cells.Item(1,$i).text();
# Concatenate the description with it's information.
$Display = " - (" + $Description + ": " + $CurrentCell.text() + ")";
# Display the information
$Writer.WriteLine($Display);
}
}
$Writer.WriteLine("");
# Keep searching that name in the current workbook until it finds no more
$SearchLast = $Range.FindNext($SearchLast);
} while (($SearchLast -ne $null) -and ($SearchLast.Row -ne $FirstRow));
} else {
$Writer.WriteLine("Not Found");
}
$Writer.WriteLine("");
};
# Cleaning up the environment
$Writer.close();
$Excel.Workbooks.Item($FileName).close();
$Excel.Quit();
# Force quit the Excel process after quitting
# Get-Process EXCEL | Stop-Process -Force;
# Then remove the $Excel com object to ready it for garbage collection
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel);
# Then, open up the Results.txt file
Invoke-Item Results.txt;

DSC Script Resource - executes .exe, but doesn't wait until completion

Question)
How do I get a DSC script resource to wait until the code has completed before moving on?
(The code is invoke-expression "path\file.exe")
Details)
I am using powershell version 5
and am trying to get DSC setup to handle our sql server installations.
My manager has asked me to use the out of the box DSC components.
i.e. no downloading of custom modules which may help.
I have built up the config file that handles the base server build - everything is good.
The script resource that installs sql server is good.
It executes, and waits until it has installed completely, before moving on.
When I get up to the script resource that installs the sql server cumulative update, I have issues.
The executable gets called and it starts installing (it should take 10-15 minutes), but the dsc configuration doesn't wait until it has installed, and moves on after a second.
This means that the DependsOn for future steps, gets called, before the installation is complete.
How can I make the script resource wait until it has finished?
Have you tried the keyword "DependsOn" like that ?
Script MyNewSvc
{
GetScript = {
$SvcName = 'MyNewSvc'
$Results = #{}
$Results['svc'] = Get-Service $SvcName
$Results
}
SetScript = {
$SvcName = 'MyNewSvc'
setup.exe /param
while((Get-Service $SvcName).Status -ne "Running"){ Start-Sleep 10 }
}
TestScript = {
$SvcName = 'MyNewSvc'
$SvcLog = 'c:\svc.log'
If (condition) { #like a a running svc or a log file
$True
}
Else {
$False
}
}
}
WindowsFeature Feature
{
Name = "Web-Server"
Ensure = "Present"
DependsOn = "[Script]MyNewSvc"
}
Invoke-Expression doesn't seem to wait until the process has finished - try this in a generic PowerShell console and you'll see the command returns before you close notepad:
Invoke-Expression -Command "notepad.exe";
You can use Start-Process instead:
Start-Process -FilePath "notepad.exe" -Wait -NoNewWindow;
And if you want to check the exit code you can do this:
$process = Start-Process -FilePath "notepad.exe" -Wait -NoNewWindow -PassThru;
$exitcode = $process.ExitCode;
if( $exitcode -ne 0 )
{
# handle errors here
}
Finally, to use command line arguments:
$process = Start-Process -FilePath "setup.exe" -ArgumentList #("/param1", "/param2") -Wait -PassThru;
$exitcode = $process.ExitCode;

Running Powershell script with if condition from a .NET app

I have a .net application that successfully runs Powershell commands that it pulls from text files- until I tried doing one that is more complicated and contains an if condition. The script works correctly from a PS console but in .NET I only know how to pass in a string for the script, which after reading the file, it adds extra stuff like vblf and even if I take it out, it won't work. Is this even possible?
.NET Runtime Error: Server was unable to process request. ---> The
term 'False' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or
if a path was included, verify that the path is correct and try again.
.NET code:
'Grab Powershell script from text (.ps1) file
strScript = File.ReadAllText(ScriptFileName)
'inject the arguments into the script
strScript = InsertArguments(strScript, Arguments)
'Open the runspace and create a pipeline if it's not already open
If psRunspace.RunspaceStateInfo.State = RunspaceState.BeforeOpen Then
psRunspace.Open()
End If
Dim MyPipeline As Pipeline = psRunspace.CreatePipeline()
MyPipeline.Commands.AddScript(strScript)
Dim psResults As Collection(Of PSObject) = MyPipeline.Invoke()
Powershell Script, stored in ps1 file:
new-mailbox -name $argument1 -DisplayName $argument1 -UserPrincipalName $argument2 Room -DomainController $argument5
if ($argument4 -eq "False") {
Set-CalendarProcessing $argument1 -BookingWindowInDays 400 -DeleteSubject $false -AutomateProcessing autoaccept -AllBookInPolicy $false -BookInPolicy $argument3 -DomainController $argument5
} else {
Set-CalendarProcessing $argument1 -BookingWindowInDays 400 -DeleteSubject $false -AutomateProcessing autoaccept -AllBookInPolicy $true -DomainController $argument5
}
When this script is read in, here is a substring of what gets pulled into strScript:
-Room -DomainController mcexdct1" & vbLf & "if (False -eq "False") {"
Fixed it by changing the PowerShell script from this:
if ($argument4 -eq "False") {
to this:
if ("$argument4" -eq "False") {
It works with quotation marks around it. I guess the "junk" I was seeing in the script string (like vblf) is normal- I don't work with reading from text files that much.

Retrieve calendar items (Outlook API, WebDAV) displaying strange behaviour

We are writing an MS Outlook plugin. To satisfy our business-logic, it should check all appointments between some dates. We are experiencing several problems with retrieving all items from calendars. We tried two options:
Outlook API. We use the standard logic that is described in MSDN - sort items by [Start], set IncludeRecurrences to True and run the Find\Restrict query over calendar items like here. It works fine in our test environment. However, in our customer's environment: For recurring appointments, start and end dates are set to the corresponding dates of a 'master appointment.' For example, in some room's calendar we have a weekly appointment that was created in January, and if we try to find all items in August, we get among others four items of this recurring appointment, but their start and end dates are set to January. But Outlook displays correct dates in the same calendar...
Very bad, but we still have WebDAV! We write a simple test application and try to query all items from the calendar using WebDAV. Of course, we didn't reinvent the wheel and just pasted the code from documentation. The previous problem is solved, but the next one arises: It doesn't return recurring items that were created more than approximately six months ago. I Haven't a clue - there are no parameters restricting 'old' items!
What is wrong? Are we missing something important?
Technical details: Exchange 2003, Outlook 2003-2010. Frankly speaking, the first error disappears if we turn on Cached Exchange Mode, but we can't do that.
var nameSpace = application.GetNamespace("MAPI");
var recepient = nameSpace.CreateRecipient(roomEMail);
recepient.Resolve();
var calendar = nameSpace.GetSharedDefaultFolder(recepient, OlDefaultFolders.olFolderCalendar);
var filter = string.Format("[Start]<'{1}' AND [End]>'{0}'",
dateFrom.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture), dateTo.ToString("dd/MM/yyyy HH:mm", CultureInfo.InvariantCulture)
);
var allItems = calendar.Items;
allItems.Sort("[Start]");
allItems.IncludeRecurrences = true;
var _item = allItems.Find(filter);
while (_item != null) {
AppointmentItem item = _item as AppointmentItem;
if (item != null) {
if (item.Subject != "some const")
&& (item.ResponseStatus != OlResponseStatus.olResponseDeclined)
&& (item.MeetingStatus != OlMeetingStatus.olMeetingReceivedAndCanceled
&& item.MeetingStatus != OlMeetingStatus.olMeetingCanceled))
{
/* Here we copy item to our internal class.
* We need: Subject, Start, End, Organizer, Recipients, MeetingStatus,
* AllDayEvent, IsRecurring, RecurrentState, ResponseStatus,
* GlobalAppointmentID */
}
}
_item = allItems.FindNext();
}
UPDATE 1:
Additional research using OutlookSpy shows that the problem is not in our code - the Start\End dates are incorrect inside the API when Cached Exchange Mode is off. But Outlook developers were aware of it, and they somehow display correct dates in calendars! Does anyone know how?
UPDATE 2:
Answer from Outlook Support Escalation Engineer:
Based on this, I can confirm that this is a problem in our product.
Possible cause:
Sort after setting IncludeRecurrences.
Here is my code of a PowerShell module that retrieves Outlook items between two dates.
And a little applet to check for changes and send an email including the agenda updates, which comes handy when you don't have mobile access to the Exchange.
Path: Documents\WindowsPowerShell\Modules\Outlook\expcal.ps1
Function Get-OutlookCalendar
{
<#
.Synopsis
This function returns appointment items from default Outlook profile
.Description
This function returns appointment items from the default Outlook profile. It uses the Outlook interop assembly to use the olFolderCalendar enumeration.
It creates a custom object consisting of Subject, Start, Duration, Location
for each appointment item.
.Example
Get-OutlookCalendar |
where-object { $_.start -gt [datetime]"5/10/2011" -AND $_.start -lt `
[datetime]"5/17/2011" } | sort-object Duration
Displays subject, start, duration and location for all appointments that
occur between 5/10/11 and 5/17/11 and sorts by duration of the appointment.
The sort is the shortest appointment on top.
.Notes
NAME: Get-OutlookCalendar
AUTHOR: ed wilson, msft
LASTEDIT: 05/10/2011 08:36:42
KEYWORDS: Microsoft Outlook, Office
HSG: HSG-05-24-2011
.Link
Http://www.ScriptingGuys.com/blog
#Requires -Version 2.0
#>
echo Starting... Initialize variables
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$olCalendarDetail = "Microsoft.Office.Interop.Outlook.OlCalendarDetail" -as [type]
echo ... Getting ref to Outlook and Calendar ...
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)
echo ... Calculating dates ...
$now = Get-Date -Hour 0 -Minute 00 -Second 00
echo From $a To $b
echo ... Getting appointments ...
$Appointments = $folder.Items
$Appointments.IncludeRecurrences = $true
$Appointments.Sort("[Start]")
echo ... Setting file names ...
$oldfile = "$env:USERPROFILE\outlook-calendar.bak"
echo oldfile: $oldfile
$newfile = "$env:USERPROFILE\outlook-calendar.txt"
echo newfile: $newfile
$calfile = "$env:USERPROFILE\outlook-calendar.ics"
echo calfile: $calfile
echo ... Exporting calendar to $calfile ...
$calendarSharing = $folder.GetCalendarExporter()
$calendarSharing.CalendarDetail = $olCalendarDetail::olFullDetails
$calendarSharing.IncludeWholeCalendar = $false
$calendarSharing.IncludeAttachments = $false
$calendarSharing.IncludePrivateDetails = $true
$calendarSharing.RestrictToWorkingHours = $false
$calendarSharing.StartDate = $now.AddDays(-30)
$calendarSharing.EndDate = $now.AddDays(30)
echo $calendarSharing
$calendarSharing.SaveAsICal($calfile)
echo ... Backing up $newfile into $oldfile ...
if (!(Test-Path $newfile)) {
echo "" |Out-File $newfile
}
# Backup old export into $oldfile
if (Test-Path $oldfile) {
echo "Deleting old backup file $oldfile"
del $oldfile
}
echo " ... moving $newfile into $oldfile ... "
move $newfile $oldfile
echo "... Generating text report to file $newfile ..."
$Appointments | Where-object { $_.start -gt $now -AND $_.start -lt $now.AddDays(+7) } |
Select-Object -Property Subject, Start, Duration, Location, IsRecurring, RecurrenceState |
Sort-object Start |
Out-File $newfile -Width 100
echo "... Comparing with previous export for changes ..."
$oldsize = (Get-Item $oldfile).length
$newsize = (Get-Item $newfile).length
if ($oldsize -ne $newsize ) {
echo "!!! Detected calendar change. Sending email..."
$mail = $outlook.CreateItem(0)
#2 = high importance email header
$mail.importance = 2
$mail.subject = $env:computername + “ Outlook Calendar“
$mail.Attachments.Add($newfile)
$mail.Attachments.Add($calfile)
$text = Get-Content $newfile | Out-String
$mail.body = “See attached file...“ + $text
#for multiple email, use semi-colon ; to separate
$mail.To = “your-email#your-mail-domain.com“
$mail.Send()
}
else {
echo "No changes detected in Calendar!"
}
} #end function Get-OutlookCalendar
Function Get-OutlookCalendarTest
{
echo starting...
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)
$a = Get-Date -Hour 0 -Minute 00 -Second 00
$b = (Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(7)
echo From $a To $b
$Appointments = $folder.Items
$Appointments.IncludeRecurrences = $true
$Appointments.Sort("[Start]")
$Appointments | Where-object { $_.start -gt $a -AND $_.start -lt $b } | Select-Object -Property IsRecurring, RecurrenceState, Subject, Start, Location
} #end function Get-OutlookCalendarTest
This is the code to invoke the PowerShell function in the module:
Path: Documents\WindowsPowerShell\mono.ps1
Import-Module -Name Outlook\expcal.psm1 -Force
$i=0
#infinite loop for calling connect function
while(1)
{
$i = $i +1
Write-Output "Running task Get-OutlookCalendar ($i)"
Get-OutlookCalendar
start-sleep -seconds 300
}
To run the PowerShell script, use powershell.exe. To run this on startup, a shortcut on "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\":
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "C:\Users\%USERNAME%\Documents\WindowsPowerShell\mono.ps1"