@dawnlit/outlook-bridge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +253 -0
- package/dist/OutlookService.d.ts +43 -0
- package/dist/OutlookService.js +137 -0
- package/dist/OutlookService.js.map +1 -0
- package/dist/errors.d.ts +112 -0
- package/dist/errors.js +140 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/mac/accounts.d.ts +5 -0
- package/dist/mac/accounts.js +17 -0
- package/dist/mac/accounts.js.map +1 -0
- package/dist/mac/attachments.d.ts +28 -0
- package/dist/mac/attachments.js +120 -0
- package/dist/mac/attachments.js.map +1 -0
- package/dist/mac/bounces.d.ts +32 -0
- package/dist/mac/bounces.js +285 -0
- package/dist/mac/bounces.js.map +1 -0
- package/dist/mac/cleanup.d.ts +29 -0
- package/dist/mac/cleanup.js +219 -0
- package/dist/mac/cleanup.js.map +1 -0
- package/dist/mac/drafts.d.ts +27 -0
- package/dist/mac/drafts.js +214 -0
- package/dist/mac/drafts.js.map +1 -0
- package/dist/mac/folders.d.ts +28 -0
- package/dist/mac/folders.js +127 -0
- package/dist/mac/folders.js.map +1 -0
- package/dist/mac/index.d.ts +22 -0
- package/dist/mac/index.js +119 -0
- package/dist/mac/index.js.map +1 -0
- package/dist/mac/read.d.ts +46 -0
- package/dist/mac/read.js +406 -0
- package/dist/mac/read.js.map +1 -0
- package/dist/mac/run.d.ts +64 -0
- package/dist/mac/run.js +250 -0
- package/dist/mac/run.js.map +1 -0
- package/dist/mac/scripts.d.ts +137 -0
- package/dist/mac/scripts.js +337 -0
- package/dist/mac/scripts.js.map +1 -0
- package/dist/mac/send.d.ts +23 -0
- package/dist/mac/send.js +135 -0
- package/dist/mac/send.js.map +1 -0
- package/dist/mac/signatures.d.ts +4 -0
- package/dist/mac/signatures.js +36 -0
- package/dist/mac/signatures.js.map +1 -0
- package/dist/mac/templates.d.ts +34 -0
- package/dist/mac/templates.js +266 -0
- package/dist/mac/templates.js.map +1 -0
- package/dist/mail.d.ts +72 -0
- package/dist/mail.js +131 -0
- package/dist/mail.js.map +1 -0
- package/dist/outlookTemplateSections.d.ts +62 -0
- package/dist/outlookTemplateSections.js +313 -0
- package/dist/outlookTemplateSections.js.map +1 -0
- package/dist/runtime.d.ts +76 -0
- package/dist/runtime.js +123 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +32 -0
- package/dist/shared/attachmentMatch.js +50 -0
- package/dist/shared/attachmentMatch.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +33 -0
- package/dist/shared/bounceRules.js +104 -0
- package/dist/shared/bounceRules.js.map +1 -0
- package/dist/shared/capabilities.d.ts +3 -0
- package/dist/shared/capabilities.js +9 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/json.d.ts +11 -0
- package/dist/shared/json.js +45 -0
- package/dist/shared/json.js.map +1 -0
- package/dist/shared/replyBody.d.ts +49 -0
- package/dist/shared/replyBody.js +113 -0
- package/dist/shared/replyBody.js.map +1 -0
- package/dist/types.d.ts +365 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/windows/accounts.d.ts +2 -0
- package/dist/windows/accounts.js +22 -0
- package/dist/windows/accounts.js.map +1 -0
- package/dist/windows/attachments.d.ts +28 -0
- package/dist/windows/attachments.js +141 -0
- package/dist/windows/attachments.js.map +1 -0
- package/dist/windows/bounces.d.ts +34 -0
- package/dist/windows/bounces.js +267 -0
- package/dist/windows/bounces.js.map +1 -0
- package/dist/windows/cleanup.d.ts +29 -0
- package/dist/windows/cleanup.js +165 -0
- package/dist/windows/cleanup.js.map +1 -0
- package/dist/windows/drafts.d.ts +29 -0
- package/dist/windows/drafts.js +253 -0
- package/dist/windows/drafts.js.map +1 -0
- package/dist/windows/folders.d.ts +16 -0
- package/dist/windows/folders.js +88 -0
- package/dist/windows/folders.js.map +1 -0
- package/dist/windows/index.d.ts +27 -0
- package/dist/windows/index.js +100 -0
- package/dist/windows/index.js.map +1 -0
- package/dist/windows/read.d.ts +54 -0
- package/dist/windows/read.js +372 -0
- package/dist/windows/read.js.map +1 -0
- package/dist/windows/run.d.ts +54 -0
- package/dist/windows/run.js +161 -0
- package/dist/windows/run.js.map +1 -0
- package/dist/windows/scripts.d.ts +56 -0
- package/dist/windows/scripts.js +163 -0
- package/dist/windows/scripts.js.map +1 -0
- package/dist/windows/send.d.ts +11 -0
- package/dist/windows/send.js +114 -0
- package/dist/windows/send.js.map +1 -0
- package/dist/windows/signatures.d.ts +14 -0
- package/dist/windows/signatures.js +69 -0
- package/dist/windows/signatures.js.map +1 -0
- package/dist/windows/templates.d.ts +25 -0
- package/dist/windows/templates.js +246 -0
- package/dist/windows/templates.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readInboxEmails = readInboxEmails;
|
|
4
|
+
exports.readEmailBody = readEmailBody;
|
|
5
|
+
exports.searchInboxByFilter = searchInboxByFilter;
|
|
6
|
+
exports.readSelectedEmail = readSelectedEmail;
|
|
7
|
+
exports.openOutlookEmail = openOutlookEmail;
|
|
8
|
+
// Reading mail: one folder, one item, a whole Inbox tree, or whatever is
|
|
9
|
+
// selected in the running Outlook.
|
|
10
|
+
const run_1 = require("./run");
|
|
11
|
+
const scripts_1 = require("./scripts");
|
|
12
|
+
const json_1 = require("../shared/json");
|
|
13
|
+
const mail_1 = require("../mail");
|
|
14
|
+
const errors_1 = require("../errors");
|
|
15
|
+
/**
|
|
16
|
+
* An arbitrary email body crosses the PowerShell boundary base64'd: it carries
|
|
17
|
+
* quotes, control characters and non-ASCII that would otherwise have to survive
|
|
18
|
+
* both the console codepage and JSON string escaping intact.
|
|
19
|
+
*/
|
|
20
|
+
function decodeBody(value) {
|
|
21
|
+
return Buffer.from((0, json_1.str)(value), 'base64').toString('utf8');
|
|
22
|
+
}
|
|
23
|
+
// Outgoing roots keep their timestamp under a different COM property; anything
|
|
24
|
+
// else carries ReceivedTime.
|
|
25
|
+
const OUTGOING_DATE_PROPS = {
|
|
26
|
+
[mail_1.WELL_KNOWN_FOLDERS['sent items']]: 'SentOn',
|
|
27
|
+
[mail_1.WELL_KNOWN_FOLDERS.outbox]: 'SentOn',
|
|
28
|
+
[mail_1.WELL_KNOWN_FOLDERS.drafts]: 'LastModificationTime',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Read recent emails from the Outlook inbox for the given account.
|
|
32
|
+
* No subject filtering — returns everything within the date window, up to the limit.
|
|
33
|
+
* Does NOT download attachments; returns attachment names only.
|
|
34
|
+
*
|
|
35
|
+
* `folder` scopes the read to one folder under the Inbox instead of the Inbox
|
|
36
|
+
* root. Without it, mail already filed away (Inbox\\Invoices) is unreachable —
|
|
37
|
+
* which is the normal state of any mailbox its owner keeps tidy. Segments are
|
|
38
|
+
* matched as direct children; a bare name that isn't a direct child falls back to
|
|
39
|
+
* the same recursive by-name search moveOutlookEmails uses, so one folder string
|
|
40
|
+
* works in both. Only the named folder is read — its own subfolders are not.
|
|
41
|
+
*/
|
|
42
|
+
async function readInboxEmails(emailAccount, daysBack = 60, limit = 50, folder) {
|
|
43
|
+
if (process.platform !== 'win32')
|
|
44
|
+
return [];
|
|
45
|
+
const ref = folder ? (0, mail_1.mailFolderRef)(folder) : { rootId: 6, rootLabel: 'Inbox', segments: [] };
|
|
46
|
+
// A folder argument that trims away to nothing ("\\", " ") would otherwise
|
|
47
|
+
// read the Inbox root and look like it had scoped — the exact silent
|
|
48
|
+
// mis-scoping this parameter exists to prevent. A bare well-known name
|
|
49
|
+
// ("Sent Items") legitimately has no segments, so it is not that case.
|
|
50
|
+
if (folder && folder.trim() && ref.segments.length === 0 && ref.rootId === 6
|
|
51
|
+
&& !Object.prototype.hasOwnProperty.call(mail_1.WELL_KNOWN_FOLDERS, folder.trim().toLowerCase())) {
|
|
52
|
+
throw new errors_1.NotFoundError('folder', `Folder '${folder}' does not name a folder under the Inbox.`);
|
|
53
|
+
}
|
|
54
|
+
// Which timestamp the folder's items actually carry (see the note in the script).
|
|
55
|
+
const dateProp = OUTGOING_DATE_PROPS[ref.rootId] ?? 'ReceivedTime';
|
|
56
|
+
const isOutgoing = (0, run_1.psBool)((0, mail_1.isOutgoingRoot)(ref.rootId));
|
|
57
|
+
const script = `${(0, scripts_1.accountScript)(emailAccount)}
|
|
58
|
+
${(0, scripts_1.namedStoreScript)(emailAccount)}
|
|
59
|
+
${(0, scripts_1.mailScopeScript)(ref, folder || '')}
|
|
60
|
+
$scopePath = $scope.FolderPath
|
|
61
|
+
$cutoff = (Get-Date).AddDays(-${daysBack}).ToString('MM/dd/yyyy HH:mm')
|
|
62
|
+
# Sent Items and Drafts carry no ReceivedTime, so restricting on it there returns
|
|
63
|
+
# an empty set that looks exactly like an empty folder. Filter each on the date
|
|
64
|
+
# property it actually has.
|
|
65
|
+
$filtered = $scope.Items.Restrict("[${dateProp}] >= '$cutoff'")
|
|
66
|
+
$filtered.Sort('[${dateProp}]', $true)
|
|
67
|
+
$results = @()
|
|
68
|
+
$cap = [Math]::Min($filtered.Count, ${limit})
|
|
69
|
+
for ($i = 1; $i -le $cap; $i++) {
|
|
70
|
+
$item = $filtered.Item($i)
|
|
71
|
+
$subj = if ($item.Subject) { $item.Subject.Trim() } else { '' }
|
|
72
|
+
$bodyText = if ($item.Body) { $item.Body } else { '' }
|
|
73
|
+
$bodyPreview = if ($bodyText.Length -gt 600) { $bodyText.Substring(0, 600) } else { $bodyText }
|
|
74
|
+
$attNames = @()
|
|
75
|
+
foreach ($att in $item.Attachments) { $attNames += $att.FileName }
|
|
76
|
+
$stamp = ''
|
|
77
|
+
try { $stamp = $item.${dateProp}.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
78
|
+
# Outgoing mail has no meaningful sender line of its own — report who it is TO,
|
|
79
|
+
# or a Sent Items listing reads as a folder of mail from yourself.
|
|
80
|
+
$who = ''
|
|
81
|
+
try { $who = if ($item.To) { $item.To } else { '' } } catch {}
|
|
82
|
+
$results += [PSCustomObject]@{
|
|
83
|
+
entryId = $item.EntryID
|
|
84
|
+
storeId = $storeId
|
|
85
|
+
folderPath = $scopePath
|
|
86
|
+
subject = $subj
|
|
87
|
+
senderName = if (${isOutgoing}) { $who } elseif ($item.SenderName) { $item.SenderName } else { '' }
|
|
88
|
+
senderEmail = if (${isOutgoing}) { $who } elseif ($item.SenderEmailAddress) { $item.SenderEmailAddress } else { '' }
|
|
89
|
+
receivedTime = $stamp
|
|
90
|
+
bodyPreview = $bodyPreview
|
|
91
|
+
attachmentNames = $attNames
|
|
92
|
+
attachmentCount = $item.Attachments.Count
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
ConvertTo-Json $results -Depth 3
|
|
96
|
+
`;
|
|
97
|
+
return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 30000)).map(item => {
|
|
98
|
+
const e = (0, json_1.record)(item);
|
|
99
|
+
return {
|
|
100
|
+
entryId: (0, json_1.str)(e.entryId),
|
|
101
|
+
storeId: (0, json_1.str)(e.storeId),
|
|
102
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
103
|
+
subject: (0, json_1.str)(e.subject),
|
|
104
|
+
senderName: (0, json_1.str)(e.senderName),
|
|
105
|
+
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
106
|
+
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
107
|
+
bodyPreview: (0, json_1.str)(e.bodyPreview),
|
|
108
|
+
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
109
|
+
attachmentCount: typeof e.attachmentCount === 'number' ? e.attachmentCount : 0,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read one email's full plain-text body by EntryID.
|
|
115
|
+
*
|
|
116
|
+
* Uses `MailItem.Body`, not `HTMLBody`: Outlook's own plain-text rendering is
|
|
117
|
+
* what every other reader here consumes, and the markup costs an order of
|
|
118
|
+
* magnitude more for the same sentences. The tradeoff is that HTML tables
|
|
119
|
+
* flatten, so figures broken out in a table lose their row/column pairing — read
|
|
120
|
+
* those from the attachment where there is one.
|
|
121
|
+
*
|
|
122
|
+
* Exists because `readInboxEmails` caps its preview at 600 chars, and a reply
|
|
123
|
+
* whose first 600 chars read as complete can still carry a material detail below
|
|
124
|
+
* the cut. Nothing detects that from the preview alone.
|
|
125
|
+
*
|
|
126
|
+
* `includeQuoted` is off by default — see splitQuotedOriginal for why the
|
|
127
|
+
* quoted thread is a hazard to a caller reading figures out of a reply.
|
|
128
|
+
* `quotedLength` is reported either way so a caller can tell it exists and ask again.
|
|
129
|
+
*/
|
|
130
|
+
async function readEmailBody(entryId, storeId, maxChars = 8000, includeQuoted = false) {
|
|
131
|
+
(0, run_1.requireWindows)();
|
|
132
|
+
const script = `
|
|
133
|
+
$outlook = New-Object -ComObject Outlook.Application
|
|
134
|
+
$ns = $outlook.GetNamespace('mapi')
|
|
135
|
+
$ns.Logon()
|
|
136
|
+
${(0, scripts_1.getItemScript)(entryId, storeId)}
|
|
137
|
+
if ($item -eq $null) { throw "Email not found for EntryID '${(0, run_1.psEscape)(entryId)}'" }
|
|
138
|
+
$cls = 0
|
|
139
|
+
try { $cls = [int]$item.Class } catch {}
|
|
140
|
+
if ($cls -ne 43 -and $cls -ne 46) { throw "The item for this EntryID is not an email (Class=$cls). Re-run readInboxEmails for a current EntryID." }
|
|
141
|
+
$bodyRaw = ''
|
|
142
|
+
try { if ($item.Body) { $bodyRaw = [string]$item.Body } } catch {}
|
|
143
|
+
$attNames = @()
|
|
144
|
+
try { foreach ($att in $item.Attachments) { $attNames += $att.FileName } } catch {}
|
|
145
|
+
${scripts_1.SENDER_SMTP_PS}
|
|
146
|
+
$received = ''
|
|
147
|
+
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
148
|
+
$out = [PSCustomObject]@{
|
|
149
|
+
entryId = $item.EntryID
|
|
150
|
+
subject = if ($item.Subject) { $item.Subject.Trim() } else { '' }
|
|
151
|
+
senderName = if ($item.SenderName) { $item.SenderName } else { '' }
|
|
152
|
+
senderEmail = $senderSmtp
|
|
153
|
+
receivedTime = $received
|
|
154
|
+
body = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
|
|
155
|
+
attachmentNames = $attNames
|
|
156
|
+
}
|
|
157
|
+
ConvertTo-Json $out -Depth 3 -Compress
|
|
158
|
+
`;
|
|
159
|
+
const raw = await (0, run_1.runPowerShell)(script, 20000);
|
|
160
|
+
if (!raw || !raw.trim())
|
|
161
|
+
throw new errors_1.NotFoundError('email', 'Failed to read email body.');
|
|
162
|
+
const parsed = (0, json_1.parseObject)(raw);
|
|
163
|
+
const { body, quoted, separator } = (0, mail_1.splitQuotedOriginal)(decodeBody(parsed.body));
|
|
164
|
+
const attachmentNames = (0, json_1.toArray)(parsed.attachmentNames).map(json_1.str);
|
|
165
|
+
// The quoted thread is context, never the sender's own answer, so it is capped
|
|
166
|
+
// harder than the reply itself — its useful part (what was asked) is at the
|
|
167
|
+
// top of it.
|
|
168
|
+
const quotedCap = Math.min(maxChars, 4000);
|
|
169
|
+
return {
|
|
170
|
+
entryId: (0, json_1.str)(parsed.entryId),
|
|
171
|
+
subject: (0, json_1.str)(parsed.subject),
|
|
172
|
+
senderName: (0, json_1.str)(parsed.senderName),
|
|
173
|
+
senderEmail: (0, json_1.str)(parsed.senderEmail),
|
|
174
|
+
receivedTime: (0, json_1.str)(parsed.receivedTime),
|
|
175
|
+
body: body.length > maxChars ? body.slice(0, maxChars) : body,
|
|
176
|
+
truncated: body.length > maxChars,
|
|
177
|
+
bodyLength: body.length,
|
|
178
|
+
quoteSeparator: separator,
|
|
179
|
+
quotedLength: quoted.length,
|
|
180
|
+
quotedOriginal: includeQuoted ? quoted.slice(0, quotedCap) : '',
|
|
181
|
+
attachmentNames,
|
|
182
|
+
attachmentCount: attachmentNames.length,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Walk every folder under the Inbox (recursively) for one account and return
|
|
187
|
+
* the emails matching `filter` — full body included, attachments listed by
|
|
188
|
+
* name but not saved.
|
|
189
|
+
*
|
|
190
|
+
* Built for a scan that doesn't know in advance which subfolder holds what
|
|
191
|
+
* it's after; `readInboxEmails` covers the cheaper "one known folder" case.
|
|
192
|
+
*
|
|
193
|
+
* `daysBack` bounds the scan to items received within that many days — what a
|
|
194
|
+
* daily batch run wants, since a subject filter alone walks the whole Inbox
|
|
195
|
+
* tree and returns every match ever received. 0 keeps that unbounded
|
|
196
|
+
* behavior, and is only sane paired with `filter.subjectLike` so Restrict can
|
|
197
|
+
* narrow the set server-side before anything crosses COM.
|
|
198
|
+
*/
|
|
199
|
+
async function searchInboxByFilter(emailAccount, filter = {}, daysBack = 0) {
|
|
200
|
+
if (process.platform !== 'win32')
|
|
201
|
+
return [];
|
|
202
|
+
const subjectLike = filter.subjectLike ? (0, run_1.psEscape)(filter.subjectLike) : '';
|
|
203
|
+
const subjectPatternSrc = filter.subjectPattern ? (0, run_1.psEscape)(filter.subjectPattern.source) : '';
|
|
204
|
+
// The subject filter reaches PowerShell as a single-quoted literal assigned to
|
|
205
|
+
// $subjLike, and the Restrict query is CONCATENATED from it. Interpolating it
|
|
206
|
+
// into the double-quoted query string directly — which is what the query needs
|
|
207
|
+
// to be, so `$cutoff` expands — would let a caller's `$(...)` execute, and
|
|
208
|
+
// would break the query outright on an apostrophe.
|
|
209
|
+
const boundedRestrict = subjectLike
|
|
210
|
+
? `$folder.Items.Restrict("[ReceivedTime] >= '$cutoff' AND [Subject] like '" + $subjLike + "'")`
|
|
211
|
+
: `$folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")`;
|
|
212
|
+
const unboundedRestrict = subjectLike
|
|
213
|
+
? `$folder.Items.Restrict("[Subject] like '" + $subjLike + "'")`
|
|
214
|
+
: `$folder.Items`;
|
|
215
|
+
const script = `
|
|
216
|
+
$subjLike = '${subjectLike}'
|
|
217
|
+
${(0, scripts_1.accountScript)(emailAccount)}
|
|
218
|
+
${(0, scripts_1.namedStoreScript)(emailAccount)}
|
|
219
|
+
$inbox = $store.GetDefaultFolder(6)
|
|
220
|
+
$results = @()
|
|
221
|
+
$seen = @{}
|
|
222
|
+
$folders = [System.Collections.ArrayList]@($inbox)
|
|
223
|
+
$fi = 0
|
|
224
|
+
while ($fi -lt $folders.Count) {
|
|
225
|
+
try { foreach ($sub in $folders[$fi].Folders) { [void]$folders.Add($sub) } } catch {}
|
|
226
|
+
$fi++
|
|
227
|
+
}
|
|
228
|
+
$daysBack = ${daysBack}
|
|
229
|
+
foreach ($folder in $folders) {
|
|
230
|
+
if ($daysBack -gt 0) {
|
|
231
|
+
# Bounded scan. Filter on BOTH date and subject in the Restrict so Outlook does the
|
|
232
|
+
# work server-side — a date-only restrict hands back every item in the window for
|
|
233
|
+
# every folder in the tree, which is thousands of COM round-trips on a busy mailbox.
|
|
234
|
+
# Any stricter subject regex still runs on whatever survives.
|
|
235
|
+
$cutoff = (Get-Date).AddDays(-$daysBack).ToString('MM/dd/yyyy HH:mm')
|
|
236
|
+
try {
|
|
237
|
+
$filtered = ${boundedRestrict}
|
|
238
|
+
$fCount = $filtered.Count
|
|
239
|
+
} catch {
|
|
240
|
+
# Some stores reject the compound query; fall back to date-only.
|
|
241
|
+
$filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
|
|
242
|
+
$fCount = $filtered.Count
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
$filtered = ${unboundedRestrict}
|
|
246
|
+
$fCount = $filtered.Count
|
|
247
|
+
${subjectLike ? `if ($fCount -eq 0) {
|
|
248
|
+
$cutoff = (Get-Date).AddDays(-60).ToString('MM/dd/yyyy HH:mm')
|
|
249
|
+
$filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
|
|
250
|
+
$fCount = $filtered.Count
|
|
251
|
+
}` : ''}
|
|
252
|
+
}
|
|
253
|
+
for ($i = 1; $i -le $fCount; $i++) {
|
|
254
|
+
$item = $filtered.Item($i)
|
|
255
|
+
if ($seen.ContainsKey($item.EntryID)) { continue }
|
|
256
|
+
$seen[$item.EntryID] = $true
|
|
257
|
+
$subject = $item.Subject
|
|
258
|
+
if (-not $subject) { continue }
|
|
259
|
+
$subject = $subject.Trim()
|
|
260
|
+
${subjectPatternSrc ? `if ($subject -notmatch '${subjectPatternSrc}') { continue }` : ''}
|
|
261
|
+
${filter.excludeReplies ? `if ($subject -imatch '^(re|fw[d]?)\\s*:') { continue }` : ''}
|
|
262
|
+
${filter.requireAttachment ? `if ($item.Attachments.Count -eq 0) { continue }` : ''}
|
|
263
|
+
$attNames = @()
|
|
264
|
+
foreach ($att in $item.Attachments) { $attNames += $att.FileName }
|
|
265
|
+
$bodyRaw = if ($item.Body) { $item.Body } else { '' }
|
|
266
|
+
$bodyB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
|
|
267
|
+
${scripts_1.SENDER_SMTP_PS}
|
|
268
|
+
$received = ''
|
|
269
|
+
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
270
|
+
$results += [PSCustomObject]@{
|
|
271
|
+
entryId = $item.EntryID
|
|
272
|
+
storeId = $storeId
|
|
273
|
+
subject = $item.Subject.Trim()
|
|
274
|
+
senderName = if ($item.SenderName) { $item.SenderName } else { '' }
|
|
275
|
+
senderEmail = $senderSmtp
|
|
276
|
+
receivedTime = $received
|
|
277
|
+
body = $bodyB64
|
|
278
|
+
attachmentNames = $attNames
|
|
279
|
+
folderPath = $folder.FolderPath
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
ConvertTo-Json $results -Depth 3
|
|
284
|
+
`;
|
|
285
|
+
// Returns whole message bodies, so this is the call most likely to push against
|
|
286
|
+
// the stdout cap — raise `maxBufferBytes` via configure() before a scan that
|
|
287
|
+
// matches thousands of emails.
|
|
288
|
+
return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 300000)).map(item => {
|
|
289
|
+
const e = (0, json_1.record)(item);
|
|
290
|
+
return {
|
|
291
|
+
entryId: (0, json_1.str)(e.entryId),
|
|
292
|
+
storeId: (0, json_1.str)(e.storeId),
|
|
293
|
+
subject: (0, json_1.str)(e.subject),
|
|
294
|
+
senderName: (0, json_1.str)(e.senderName),
|
|
295
|
+
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
296
|
+
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
297
|
+
body: decodeBody(e.body),
|
|
298
|
+
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
299
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Read the email currently selected (or open) in Outlook — full body,
|
|
305
|
+
* attachments listed by name but not saved.
|
|
306
|
+
*/
|
|
307
|
+
async function readSelectedEmail() {
|
|
308
|
+
(0, run_1.requireWindows)();
|
|
309
|
+
const script = `
|
|
310
|
+
$outlook = New-Object -ComObject Outlook.Application
|
|
311
|
+
$item = $null
|
|
312
|
+
try {
|
|
313
|
+
$explorer = $outlook.ActiveExplorer()
|
|
314
|
+
if ($explorer -ne $null) {
|
|
315
|
+
$sel = $explorer.Selection
|
|
316
|
+
if ($sel -ne $null -and $sel.Count -ge 1) { $item = $sel.Item(1) }
|
|
317
|
+
}
|
|
318
|
+
} catch {}
|
|
319
|
+
if ($item -eq $null) {
|
|
320
|
+
try {
|
|
321
|
+
$insp = $outlook.ActiveInspector()
|
|
322
|
+
if ($insp -ne $null) { $item = $insp.CurrentItem }
|
|
323
|
+
} catch {}
|
|
324
|
+
}
|
|
325
|
+
if ($item -eq $null) { throw 'No email is selected in Outlook. Open Outlook, select (or open) an email, then try again.' }
|
|
326
|
+
if ($item.MessageClass -notlike 'IPM.Note*') { throw 'The selected Outlook item is not an email.' }
|
|
327
|
+
$attNames = @()
|
|
328
|
+
foreach ($att in $item.Attachments) { $attNames += $att.FileName }
|
|
329
|
+
$subject = ''
|
|
330
|
+
if ($item.Subject) { $subject = $item.Subject.Trim() }
|
|
331
|
+
$bodyRaw = if ($item.Body) { $item.Body } else { '' }
|
|
332
|
+
$bodyB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
|
|
333
|
+
$storeId = ''
|
|
334
|
+
try { $storeId = $item.Parent.Store.StoreID } catch {}
|
|
335
|
+
${scripts_1.SENDER_SMTP_PS}
|
|
336
|
+
$received = ''
|
|
337
|
+
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
338
|
+
$result = [PSCustomObject]@{
|
|
339
|
+
entryId = $item.EntryID
|
|
340
|
+
storeId = $storeId
|
|
341
|
+
subject = $subject
|
|
342
|
+
senderName = if ($item.SenderName) { $item.SenderName } else { '' }
|
|
343
|
+
senderEmail = $senderSmtp
|
|
344
|
+
receivedTime = $received
|
|
345
|
+
body = $bodyB64
|
|
346
|
+
attachmentNames = $attNames
|
|
347
|
+
}
|
|
348
|
+
ConvertTo-Json $result -Depth 3
|
|
349
|
+
`;
|
|
350
|
+
const raw = await (0, run_1.runPowerShell)(script, 30000);
|
|
351
|
+
if (!raw || raw.trim() === '' || raw.trim() === 'null') {
|
|
352
|
+
throw new errors_1.NotFoundError('email', 'Failed to read the selected Outlook email.');
|
|
353
|
+
}
|
|
354
|
+
const e = (0, json_1.parseObject)(raw);
|
|
355
|
+
return {
|
|
356
|
+
entryId: (0, json_1.str)(e.entryId),
|
|
357
|
+
storeId: (0, json_1.str)(e.storeId),
|
|
358
|
+
subject: (0, json_1.str)(e.subject),
|
|
359
|
+
senderName: (0, json_1.str)(e.senderName),
|
|
360
|
+
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
361
|
+
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
362
|
+
body: decodeBody(e.body),
|
|
363
|
+
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/** Open an email in Outlook by its EntryID. */
|
|
367
|
+
async function openOutlookEmail(entryId) {
|
|
368
|
+
(0, run_1.requireWindows)();
|
|
369
|
+
const script = `(New-Object -ComObject Outlook.Application).GetNamespace('mapi').GetItemFromID('${(0, run_1.psEscape)(entryId)}').Display()`;
|
|
370
|
+
await (0, run_1.runPowerShell)(script);
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/windows/read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse before generating anything when this isn't Windows.
|
|
3
|
+
*
|
|
4
|
+
* Called at the top of each operation as well as inside the two runners, so a
|
|
5
|
+
* call that would have written temp files or made preparatory reads doesn't do
|
|
6
|
+
* any of that first. The runners hold the only copy of the check itself — three
|
|
7
|
+
* near-identical guards worded three different ways is how "not supported" came
|
|
8
|
+
* to mean two different sentences.
|
|
9
|
+
*/
|
|
10
|
+
export declare function requireWindows(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Escape a string for a PowerShell SINGLE-quoted literal — the only context
|
|
13
|
+
* caller-supplied text may go in.
|
|
14
|
+
*
|
|
15
|
+
* A double-quoted PowerShell string expands `$(...)` subexpressions, so text
|
|
16
|
+
* placed there would execute. Where a generated script needs user text inside a
|
|
17
|
+
* double-quoted string (a Restrict query, which must expand `$cutoff`), assign
|
|
18
|
+
* it to a variable with this first and concatenate — see read.ts.
|
|
19
|
+
*/
|
|
20
|
+
export declare function psEscape(s: string): string;
|
|
21
|
+
/** A PowerShell array literal of single-quoted strings. */
|
|
22
|
+
export declare function psList(values: readonly string[]): string;
|
|
23
|
+
/** `$true` / `$false` — PowerShell has no bare boolean literal. */
|
|
24
|
+
export declare function psBool(value: boolean): string;
|
|
25
|
+
/**
|
|
26
|
+
* Run a generated script and return its stdout.
|
|
27
|
+
*
|
|
28
|
+
* `timeout` overrides the configured default for this one call — pass it where a
|
|
29
|
+
* call has a genuinely different budget (a full-mailbox walk, a purge) rather
|
|
30
|
+
* than relying on the global. See `configure()` for both knobs.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runPowerShell(script: string, timeout?: number): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Run a generated script from a temp `.ps1` file rather than `-Command`.
|
|
35
|
+
*
|
|
36
|
+
* For the two calls that drive a compose window: a script passed on the command
|
|
37
|
+
* line competes with the command-line length limit, and these carry enough
|
|
38
|
+
* inline text to reach it. Failures classify exactly as `runPowerShell`'s do —
|
|
39
|
+
* the file is an implementation detail, not a second error contract.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runPowerShellFile(script: string, timeout?: number): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Write text a script will read back, and hand back a cleanup callback.
|
|
44
|
+
*
|
|
45
|
+
* Large HTML — an email body, a Word-sized template — goes to a file rather than
|
|
46
|
+
* into the script text: inline, it is what pushes a generated script past the
|
|
47
|
+
* command-line length limit.
|
|
48
|
+
*/
|
|
49
|
+
export declare function scriptInput(kind: string, contents: string): {
|
|
50
|
+
path: string;
|
|
51
|
+
cleanup(): void;
|
|
52
|
+
};
|
|
53
|
+
/** Read a file written by a script, as UTF-8, or '' when it never appeared. */
|
|
54
|
+
export declare function readScriptOutput(path: string): string;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.requireWindows = requireWindows;
|
|
7
|
+
exports.psEscape = psEscape;
|
|
8
|
+
exports.psList = psList;
|
|
9
|
+
exports.psBool = psBool;
|
|
10
|
+
exports.runPowerShell = runPowerShell;
|
|
11
|
+
exports.runPowerShellFile = runPowerShellFile;
|
|
12
|
+
exports.scriptInput = scriptInput;
|
|
13
|
+
exports.readScriptOutput = readScriptOutput;
|
|
14
|
+
// Getting a generated script to Windows PowerShell and its output back.
|
|
15
|
+
//
|
|
16
|
+
// Every Windows function here is "build a script, run it, coerce the JSON", so
|
|
17
|
+
// this module owns the two halves nobody should reimplement: the run itself
|
|
18
|
+
// (encoding, timeout, cancellation, failure classification, the debug hook) and
|
|
19
|
+
// the escaping rules for putting caller text into a script safely.
|
|
20
|
+
const child_process_1 = require("child_process");
|
|
21
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
|
+
const runtime_1 = require("../runtime");
|
|
23
|
+
const errors_1 = require("../errors");
|
|
24
|
+
const RUNNER = 'powershell';
|
|
25
|
+
/**
|
|
26
|
+
* Refuse before generating anything when this isn't Windows.
|
|
27
|
+
*
|
|
28
|
+
* Called at the top of each operation as well as inside the two runners, so a
|
|
29
|
+
* call that would have written temp files or made preparatory reads doesn't do
|
|
30
|
+
* any of that first. The runners hold the only copy of the check itself — three
|
|
31
|
+
* near-identical guards worded three different ways is how "not supported" came
|
|
32
|
+
* to mean two different sentences.
|
|
33
|
+
*/
|
|
34
|
+
function requireWindows() {
|
|
35
|
+
if (process.platform !== 'win32') {
|
|
36
|
+
throw new errors_1.UnsupportedPlatformError(process.platform, 'Outlook COM automation is only supported on Windows.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Escape a string for a PowerShell SINGLE-quoted literal — the only context
|
|
41
|
+
* caller-supplied text may go in.
|
|
42
|
+
*
|
|
43
|
+
* A double-quoted PowerShell string expands `$(...)` subexpressions, so text
|
|
44
|
+
* placed there would execute. Where a generated script needs user text inside a
|
|
45
|
+
* double-quoted string (a Restrict query, which must expand `$cutoff`), assign
|
|
46
|
+
* it to a variable with this first and concatenate — see read.ts.
|
|
47
|
+
*/
|
|
48
|
+
function psEscape(s) {
|
|
49
|
+
return s.replace(/'/g, "''");
|
|
50
|
+
}
|
|
51
|
+
/** A PowerShell array literal of single-quoted strings. */
|
|
52
|
+
function psList(values) {
|
|
53
|
+
return values.map(v => `'${psEscape(v)}'`).join(',');
|
|
54
|
+
}
|
|
55
|
+
/** `$true` / `$false` — PowerShell has no bare boolean literal. */
|
|
56
|
+
function psBool(value) {
|
|
57
|
+
return value ? '$true' : '$false';
|
|
58
|
+
}
|
|
59
|
+
// Force UTF-8 on stdout. Windows PowerShell 5.1 otherwise encodes output in the
|
|
60
|
+
// OEM/ANSI console code page, whose "best-fit" mapping silently rewrites
|
|
61
|
+
// non-ASCII characters — e.g. a curly quote (U+201C) in an email body becomes a
|
|
62
|
+
// plain " — which corrupts the JSON these scripts emit (an unescaped quote) and
|
|
63
|
+
// breaks JSON.parse. Setting the output encoding first makes non-ASCII survive
|
|
64
|
+
// as real UTF-8 bytes, which Node then decodes correctly.
|
|
65
|
+
const UTF8_PRELUDE = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n';
|
|
66
|
+
/**
|
|
67
|
+
* Run a generated script and return its stdout.
|
|
68
|
+
*
|
|
69
|
+
* `timeout` overrides the configured default for this one call — pass it where a
|
|
70
|
+
* call has a genuinely different budget (a full-mailbox walk, a purge) rather
|
|
71
|
+
* than relying on the global. See `configure()` for both knobs.
|
|
72
|
+
*/
|
|
73
|
+
function runPowerShell(script, timeout) {
|
|
74
|
+
requireWindows();
|
|
75
|
+
const utf8Script = UTF8_PRELUDE + script;
|
|
76
|
+
return spawnPowerShell(['-NoProfile', '-NonInteractive', '-Command', utf8Script], utf8Script, timeout);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run a generated script from a temp `.ps1` file rather than `-Command`.
|
|
80
|
+
*
|
|
81
|
+
* For the two calls that drive a compose window: a script passed on the command
|
|
82
|
+
* line competes with the command-line length limit, and these carry enough
|
|
83
|
+
* inline text to reach it. Failures classify exactly as `runPowerShell`'s do —
|
|
84
|
+
* the file is an implementation detail, not a second error contract.
|
|
85
|
+
*/
|
|
86
|
+
async function runPowerShellFile(script, timeout) {
|
|
87
|
+
requireWindows();
|
|
88
|
+
const scriptFile = (0, runtime_1.tempFile)('script', 'ps1');
|
|
89
|
+
fs_1.default.writeFileSync(scriptFile, UTF8_PRELUDE + script, 'utf-8');
|
|
90
|
+
try {
|
|
91
|
+
return await spawnPowerShell(['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptFile], script, timeout);
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
try {
|
|
95
|
+
fs_1.default.unlinkSync(scriptFile);
|
|
96
|
+
}
|
|
97
|
+
catch { /* the run is done; a leftover scratch file is not worth failing on */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** The one place a PowerShell child is spawned, timed, classified and reported. */
|
|
102
|
+
function spawnPowerShell(args, script, timeout) {
|
|
103
|
+
const { timeoutMs, maxBufferBytes, signal } = (0, runtime_1.getConfig)();
|
|
104
|
+
const effectiveTimeout = timeout ?? timeoutMs;
|
|
105
|
+
const startedAt = Date.now();
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
(0, child_process_1.execFile)('powershell.exe', args, { maxBuffer: maxBufferBytes, timeout: effectiveTimeout, signal }, (error, stdout, stderr) => {
|
|
108
|
+
const durationMs = Date.now() - startedAt;
|
|
109
|
+
if (error) {
|
|
110
|
+
const failure = (0, errors_1.classifyRunFailure)({
|
|
111
|
+
runner: RUNNER,
|
|
112
|
+
script,
|
|
113
|
+
stderr: (stderr || error.message).trim(),
|
|
114
|
+
durationMs,
|
|
115
|
+
nodeError: error,
|
|
116
|
+
timeoutMs: effectiveTimeout,
|
|
117
|
+
signal,
|
|
118
|
+
});
|
|
119
|
+
(0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs, error: failure.message });
|
|
120
|
+
reject(failure);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
(0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs });
|
|
124
|
+
resolve(stdout.trim());
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Write text a script will read back, and hand back a cleanup callback.
|
|
131
|
+
*
|
|
132
|
+
* Large HTML — an email body, a Word-sized template — goes to a file rather than
|
|
133
|
+
* into the script text: inline, it is what pushes a generated script past the
|
|
134
|
+
* command-line length limit.
|
|
135
|
+
*/
|
|
136
|
+
function scriptInput(kind, contents) {
|
|
137
|
+
const path = (0, runtime_1.tempFile)(kind, 'html');
|
|
138
|
+
fs_1.default.writeFileSync(path, contents, 'utf-8');
|
|
139
|
+
return {
|
|
140
|
+
path,
|
|
141
|
+
cleanup() {
|
|
142
|
+
try {
|
|
143
|
+
fs_1.default.unlinkSync(path);
|
|
144
|
+
}
|
|
145
|
+
catch { /* ignore */
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Read a file written by a script, as UTF-8, or '' when it never appeared. */
|
|
151
|
+
function readScriptOutput(path) {
|
|
152
|
+
try {
|
|
153
|
+
const contents = fs_1.default.readFileSync(path, 'utf-8');
|
|
154
|
+
fs_1.default.unlinkSync(path);
|
|
155
|
+
return contents;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/windows/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MailFolderRef } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Session plus the account whose SMTP address matches; binds `$target` and
|
|
4
|
+
* `$account`.
|
|
5
|
+
*
|
|
6
|
+
* Fails rather than falling back to the default account. A bad address must
|
|
7
|
+
* never silently send from — or read — the wrong mailbox, and the thrown
|
|
8
|
+
* sentence is the one `classifyRunFailure` recognises to raise
|
|
9
|
+
* `AccountNotFoundError`, so the wording is load-bearing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function accountScript(emailAccount: string): string;
|
|
12
|
+
/** The account's own delivery store, as `$store`. Requires `accountScript`. */
|
|
13
|
+
export declare const DELIVERY_STORE_PS = "\n$store = $account.DeliveryStore\n$storeId = $store.StoreID\n";
|
|
14
|
+
/**
|
|
15
|
+
* The account's store reached through the namespace's top-level folders, as
|
|
16
|
+
* `$storeFolder` / `$store`.
|
|
17
|
+
*
|
|
18
|
+
* Kept alongside `DELIVERY_STORE_PS` rather than replaced by it: the reading
|
|
19
|
+
* operations resolve the store this way, and on a profile where a mailbox is
|
|
20
|
+
* open under a display name that differs from the delivery store's, the two do
|
|
21
|
+
* not select the same folders. Changing which one a reader uses changes what it
|
|
22
|
+
* returns, so each keeps the route it was verified against.
|
|
23
|
+
*/
|
|
24
|
+
export declare function namedStoreScript(emailAccount: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve one item into `$item`. A StoreID disambiguates across mailboxes, so
|
|
27
|
+
* pass it whenever the listing that produced the id carried one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getItemScript(entryId: string, storeId?: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* The sender's real SMTP address, as `$senderSmtp`. Requires `$item`.
|
|
32
|
+
*
|
|
33
|
+
* An Exchange sender's `SenderEmailAddress` is an X500 DN rather than an
|
|
34
|
+
* address; a caller that means to reply to it, or correlate it with anything,
|
|
35
|
+
* needs the address it resolves to.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SENDER_SMTP_PS = "\n$senderSmtp = ''\ntry {\n if ($item.SenderEmailType -eq 'EX') { $senderSmtp = $item.Sender.GetExchangeUser().PrimarySmtpAddress }\n} catch {}\nif (-not $senderSmtp) { $senderSmtp = if ($item.SenderEmailAddress) { $item.SenderEmailAddress } else { '' } }\n";
|
|
38
|
+
/**
|
|
39
|
+
* Recursive folder-by-name search. Case-insensitive, depth-capped so a huge
|
|
40
|
+
* mailbox tree can't hang the scan.
|
|
41
|
+
*/
|
|
42
|
+
export declare const FIND_FOLDER_PS = "\nfunction Find-FolderByName($root, $name, $depth) {\n foreach ($f in $root.Folders) {\n if ($f.Name -ieq $name) { return $f }\n }\n if ($depth -le 1) { return $null }\n foreach ($f in $root.Folders) {\n $hit = Find-FolderByName $f $name ($depth - 1)\n if ($hit -ne $null) { return $hit }\n }\n return $null\n}\n";
|
|
43
|
+
/**
|
|
44
|
+
* Emit the PowerShell that resolves `$scope` from `$store`, walking a well-known
|
|
45
|
+
* root down through any further segments. Also binds `$scopeCreated`.
|
|
46
|
+
*
|
|
47
|
+
* Split out so the emitted script can be parse-checked without an Outlook
|
|
48
|
+
* session — the folder walk is the only part that varies per call, and a syntax
|
|
49
|
+
* error in it would only ever surface as a failed live run.
|
|
50
|
+
*
|
|
51
|
+
* `folderLabel` is the caller's original string, used verbatim in the error so
|
|
52
|
+
* the message names what they typed rather than the parsed segments. Set
|
|
53
|
+
* `createMissing` to build absent segments instead of throwing — the whole
|
|
54
|
+
* chain, so a nested destination can be created in one call.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mailScopeScript(ref: MailFolderRef, folderLabel?: string, createMissing?: boolean): string;
|