I'm trying to use PowerShell to create a new meeting. We have a requirement to disable the "Allow New Time Proposal" button for this meeting. My script works to create the meeting but I'm unable to find the property on the meeting object.
The option is available in the Outlook UI
I've reviewed the Microsoft documents but I don't see an option disable "Allow New Time Proposal".
Here is my code PowerShell code.
$Outlook = New-Object -comobject Outlook.Application
$Meeting = $Outlook.CreateItem($olItemType.olAppointmentItem)
$Meeting.AllDayEvent = $True
$Meeting.Body = "Review DeathStar design for vulnerabilities"
$Meeting.BusyStatus = 1
$Meeting.DoNotforwardMeeting = $True
$Meeting.End = $End
$Meeting.Location = "Grand Moff Tarkin meeting hall"
$Meeting.Recipients.Add('Moff.Gideon#ImperialDeptMilitaryResearch.GalaticEmpire.Gov')
$Meeting.ReminderSet = $False
$Meeting.ResponseRequested = $false
$Meeting.Start = $Start
$Meeting.Subject = "DeathStar Vulnerability Planning"
I'm looking for a solution in either PowerShell or VBA, as long as it doesn't require direct interaction with the Outlook UI.
You need to set AppointmentNotAllowPropose named MAPI property (DASL name "http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/825A000B") to true using AppointmentItem.PropertyAccessor.SetProperty:
$Meeting = $Outlook.CreateItem($olItemType.olAppointmentItem)
$Meeting.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/825A000B", $true)
If you need to see MAPI properties (and their DASL names) not explicitly exposed by Outlook, take a look at the appointment with OutlookSpy (I am its author, click IMessage button).
Related
The following requirement(s) I have:
As domain admin user logged on to an administrative client machine
I want to perform some changes on an exchange server using calls
from vba(excel 2013) via powershell to an exchange server (2013).
The client machine runs Windows 10 (1809) and powershell v5.1.17763.1
Upon a button press in the vba excel form I want to perform a trivial
task like getting all info for a specific mailbox user, reading
the results back in from stdout/stderr using WSH.Shell, later on more to come.
Executing the command below does what it shall, with the following two drawbacks:
1) the credentials are still asked again for though already passed to the ScriptBlock as $Cred via -ArgumentList
2) the powershell window does not close automatically after processing, it needs
to be closed actively by the user
Finally, the retrieved stdout/stderr gets me what I want (by the way, is there a direct connection possible as to have the powershell objects retrieved into a vba collection?)
WORKS on commandline (a "one-liner"), yet have to provide credentials via popup:
powershell -Command { $Username = 'MYDOMAIN\Administrator'; $Password = 'foobar'; $pass = ConvertTo-SecureString -AsPlainText $Password -Force; $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass; Invoke-Command -ComputerName Exchange -ArgumentList $Cred -ScriptBlock { $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange.somewhere.com/PowerShell/ -Authentication Kerberos -Credential $Cred; Import-PSSession $Session; Get-Mailbox MYUSER; Remove-PSSession $Session } }
WORKS from vba via WSH.Shell Exec, yet have to provide credentials via popup and have to actively close the powershell console window
(and see me avoiding double quotes (") within the powershell script, havent figured out yet how to escape them correctly ("" doesnt work)):
powershell -Command "& { $Username = 'MYDOMAIN\Administrator'; $Password = 'foobar'; $pass = ConvertTo-SecureString -AsPlainText $Password -Force; $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass; Invoke-Command -ComputerName Exchange -ArgumentList $Cred -ScriptBlock { $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange.somewhere.com/PowerShell/ -Authentication Kerberos -Credential $Cred; Import-PSSession $Session; Get-Mailbox MYUSER; Remove-PSSession $Session } }"
So basically it is writing
powershell -Command "& { ... }"
in vba when called via 'wsh shell exec' instead of
powershell -Command { ... }
on the commandline, this seems to be required to retrieve stdin/stdout correctly, would be glad for suggestions why this is the case or if there is an alternative style to write this, too.
Any suggestions how to get rid of the powershell popup asking for the credential
and how to get rid of the powershell window not going away automatically?
Thanks,
Jeff
P.S.:
For your reference, the vba method to do the powershell call (End Function and #if stuff is broken in the code block, you'll figure it out though):
Public Function execPSCommand(ByVal psCmd As String, Optional ByVal label As String = "Debug") As String()
Dim oShell, oExec
Dim retval(2) As String
retval(0) = ""
retval(1) = ""
Set oShell = CreateObject("WScript.Shell")
Set oExec = oShell.Exec(psCmd)
oExec.stdin.Close ' close standard input before reading output
Const WshRunning = 0
Do While oExec.Status = WshRunning
Sleep 100 ' Sleep a tenth of a second
Loop
Dim stdout As String
Dim stderr As String
stdout = oExec.stdout.ReadAll
stderr = oExec.stderr.ReadAll
retval(0) = stdout
retval(1) = stderr
execPSCommand = retval()
End Function
' Sleep (fractions of a second, milliseconds to be precise) for VBA
'#If VBA7 Then
' Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'#Else
' Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'#End If
I think you are not passing $cred argument properly to the scriptblock. The scriptblock should start with param($cred) if you want to use that local variable. Why not define $cred inside the scriptblock though? You can also use Using modifier to push local variable to the remote command (like $Using:cred, see more details https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_variables?view=powershell-6 )
Regarding exiting powershell at the end, I guess you can just type "Exit" or "Stop-Process $pid" at the end of your command.
#Mike: Great stuff, thanks a lot for pointing me into the right direction.
The solution for me was to add "param([PSCredential]$Cred); " as suggested by you.
Of course I could have created $Cred inside the ScriptBlock as well ;)
Furthermore I remembered to have read somewhere that a PSSession should be
closed by a Remove-PSSession command afterwards in order to free up resources.
BUT: this approach clashed with the way I was busy waiting for the wsh shell command to finish (in vba) and then reading stdout/stderr from the process
expecting to close soon - so I removed the busy wait in vba for this particular
case (remote ps session).
It turned out that I did not need to call Remove-PSsession at all, cross-checked
that with a Start-Sleep 60 as the last command (instead of Remove-PSsession)
and executed "Get-PSsession -ComputerName exchange" once in a while on a
real powershell console while execution went on in vba; as soon as the 60 seconds
did pass the session was cleaned up automatically (no more sessions listed).
So the short story is: omit busy waiting when a remote PSSession is done in the ps script (something behind the scenes seems to already have removed that process or anything else not yet clear to me gets in the way in a blocking manner) - why oExec.Status is still left on WshRunning in the 'busy waiting
case' is beyond me, I was expecting it to be either WshFinished or WshFailed,
but that way it caused a blocking powershell window waiting forever.
Anyway, hardcoded vba password is gone as well, read in instead using an inputbox now,
happy powershelling may continue ;)
I am using the following approach in a VSTO Outlook Addin (using Addin-Express library) to set a custom x-header attribute on mails and other items you can send from Microsoft Outlook, like meetings. It's a security classification which is evaluated by a mail gateway appliance for making sure encryption is activated on outgoing mails, depending on the classification. Inside the organization, it will just be displayed on the receiving side, which is Outlook 2016 desktop client.
Before sending, I set the x-header property like this:
string headerNamespace = "http://schemas.microsoft.com/mapi/string /{00020386-0000-0000-C000-000000000046}/";
public void SetHeader(PropertyAccessor acc, string header, string value)
{
acc.SetProperty(headerNamespace + "x-mycustomheader", value);
}
I always receive the header for mails, but not for Meetings. I know there is an associated appointment, but I tried reading the header from any object I could think of
On the receiving end, I get the current item from the explorer or inspector window and try to retrieve that header. The attribute is not visible in OutlookSpy and it's not inside the transport header. Is it possible that Outlook stored it somewhere else, or has it been removed? This process is working fine for MailItem types.
I stripped some parts like releasing the com objects for better readability. OutlookMeetingItem2 is a wrapper class. I have two alternative methods for reading the header, one is the PropertyAccessor and the other is used here, both can't find the header attribute.
using (OutlookMessageItemWrapper outlookItem = OutlookMessageItemFactory.GetMessageItem(currentItem))
{
object outlookitemMapi = outlookItem.MAPIOBJECT;
classificationString = mapi.GetHeader(outlookItem.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "" && outlookItem is OutlookMeetingItem2)
{
OutlookMeetingItem2 meetingItem = outlookItem as OutlookMeetingItem2;
MeetingItem meeting = meetingItem.Item;
object mapiObject = meeting.MAPIOBJECT;
classificationString = mapi.GetHeader(meeting.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
if (classificationString == "")
{
AppointmentItem appointment = meeting.GetAssociatedAppointment(false);
if (appointment != null)
{
classificationString = mapi.GetHeader(appointment.MAPIOBJECT, Constants.CLASSIFICATIONHEADER);
}
}
}
}
So here is what I want to do:
I have a VBA-application that uses information of multiple open Word-2010 Documents. This works fine as long as all open Documents are opened within the same process. But I would like to open these Documents not manually but via Powershell and so far I have only been able to do so by starting a new instance of Word. Let's have a look at the code:
$isRunning = (Get-Process | Where-Object { $_.Name -eq "WINWORD" }).Count -gt 0
if ($isRunning) {
#Select open Word Process
**$wrd = # ???**
# Make Word Visible
$wrd.visible = $true
# Open a document
$doc = $wrd.documents.add($latest.Fullname)
}
else {
# Create Word Object
$wrd = New-object -com word.application
# Make Word Visible
$wrd.visible = $true
# Open a document
$doc = $wrd.documents.add($latest.Fullname)
}
The ??? mark the spot where I would like to Select a running instance of Word in which I can open my docs. But all examples I could find always invoke a new object by starting a separate instance of word.
Another workaround would be to change my VBA application to access Documents of different processes, but I don't know if that is even possible or how to do it.
Either way, any help would be highly appreciated.
Thanks.
You can get a running instance like this:
$word = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application')
Currently, the user adds a "new internet calendar", but it's a one-time download of the ICS file. I want the user to click a button to get his personal calendar added as a subscription to Outlook. I want the automatically updating "internet calendar subscription".
Like in SharePoint, the button called "Connect to Outlook" which adds the calendar you're viewing to your Outlook as an automatically syncing calendar.
Creating iCals in C# and this CodeProject post tell me you should use the DDay iCal Library.
DDay.iCal is an iCal (RFC 5545) class library for .NET 2.0 and above, Silverlight. It aims at being as RFC 5545 compliant as possible, while targeting compatibility with popular calendaring applications, like Apple iCal, Outlook 2007, etc.
Some sample code of iCal + MVC + DDay.iCal
public ActionResult iCalendar(string DownloadFileName)
{
DDay.iCal.iCalendar iCal = new DDay.iCal.iCalendar();
Event evt = iCal.Create<Event>();
evt.Start = iCalDateTime.Today.AddHours(8);
evt.End = evt.Start.AddHours(18); // This also sets the duration
evt.Description = "The event description";
evt.Location = "Event location";
evt.Summary = "18 hour event summary";
evt = iCal.Create<Event>();
evt.Start = iCalDateTime.Today.AddDays(5);
evt.End = evt.Start.AddDays(1);
evt.IsAllDay = true;
evt.Summary = "All-day event";
ISerializationContext ctx = new SerializationContext();
ISerializerFactory factory = new DDay.iCal.Serialization.iCalendar.SerializerFactory();
IStringSerializer serializer = factory.Build(iCal.GetType(), ctx) as IStringSerializer;
string output = serializer.SerializeToString(iCal);
var contentType = "text/calendar";
var bytes = Encoding.UTF8.GetBytes(output);
return File(bytes, contentType, DownloadFileName);
}
I have a JScript script that runs using cscript.exe. It creates a shortcut on the desktop (and in the start menu) that runs cscript.exe with parameters to run another JScript script. It looks, in relevant part, like this:
function create_shortcut_at(folder, target_script_folder)
{
var shell = new ActiveXObject("WScript.Shell");
var shortcut = shell.CreateShortcut(folder + "\\Run The Script.lnk");
shortcut.TargetPath = "cscript";
shortcut.Arguments = "\""+target_script_folder+"\\script.js\" /aParam /orTwo";
shortcut.IconLocation = target_script_folder+"\\icon.ico";
shortcut.Save();
}
It gets called like create_shortcut_at(desktop_folder, script_folder).
And that works, as far as it goes. It creates the desktop icon, pointing properly to the script and runs it when double-clicked. The problem is that it really needs to run the script "as administrator".
And the script really does need to run "as administrator" -- it installs applications (for all users) and reboots the computer. (For those interested, the script is wpkg.js. Modifying it to self-elevate is undesirable.)
Since the target of the shortcut is actually "cscript.exe", I can't use a manifest for the escalation. I could probably theoretically install a cscript.exe.manifest in the windows directory, but even if that worked, it would be a terrible idea for reasons that are obvious.
I'd also prefer not to use a dummy script, since that is an extra file to deal with and there's another, seemingly reasonable, solution at hand: check the "Run as administrator" box on the shortcut.
Thirty-seconds of investigation reveals that the WScript.Shell ActiveX Object does not have the interfaces required for this. Additional investigation suggests that IShellLinkDataList does. However, IShellLinkDataList is a generic COM Interface. I see several examples around the Internet, most linking here. However, all the examples do it in compiled code (C++, C#, even JScript.NET). I significantly prefer to be able to do it directly in JScript, running from cscript.exe.
That said, I'm all for ideas I didn't contemplate or other solutions.
The official way to mark a shortcut file as requiring elevation is via IShellLinkDataList. It's difficult to use that interface from an automation environment.
But, if you are happy with a hack, you can do it in script, just by flipping a bit in the .lnk file.
When you tick the "run as administrator" box in the Advanced tab of the Shell Properties box, or when you use IShellLinkDataList to set the flags to include SLDF_RUNAS_USER, you're basically just setting one bit in the file.
You can do that "manually" without going through the COM interface. It's byte 21, and you need to set the 0x20 bit on.
(function(globalScope) {
'use strict';
var fso = new ActiveXObject("Scripting.FileSystemObject"),
path = "c:\\path\\goes\\here\\Shortcut2.lnk",
shortPath = path.split('\\').pop(),
newPath = "new-" + shortPath;
function readAllBytes(path) {
var ts = fso.OpenTextFile(path, 1), a = [];
while (!ts.AtEndOfStream)
a.push(ts.Read(1).charCodeAt(0));
ts.Close();
return a;
}
function writeBytes(path, data) {
var ts = fso.CreateTextFile(path, true),
i=0, L = data.length;
for (; i<L; i++) {
ts.Write(String.fromCharCode(data[i]));
}
ts.Close();
}
function makeLnkRunAs(path, newPath) {
var a = readAllBytes(path);
a[0x15] |= 0x20; // flip the bit.
writeBytes(newPath, a);
}
makeLnkRunAs(path, newPath);
}(this));
ps:
function createShortcut(targetFolder, sourceFolder){
var shell = new ActiveXObject("WScript.Shell"),
shortcut = shell.CreateShortcut(targetFolder + "\\Run The Script.lnk"),
fso = new ActiveXObject("Scripting.FileSystemObject"),
windir = fso.GetSpecialFolder(specialFolders.windowsFolder);
shortcut.TargetPath = fso.BuildPath(windir,"system32\\cscript.exe");
shortcut.Arguments = "\"" + sourceFolder + "\\script.js\" /aParam /orTwo";
shortcut.IconLocation = sourceFolder + "\\icon.ico";
shortcut.Save();
}