Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 36 additions & 23 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ repository (`Sekers/ScriptMessage.wiki`), not part of this one.
`Tests/` use whichever configuration their `Set-ScriptMessageConfigFilePath` line names; read that line, and
the recipients, before running anything.
- **Never name the production tenant in any file.** No organization name, abbreviation, or email domain in
code, comments, help examples, tests, or commit messages. Use placeholder addresses on `domain.com`, as the
existing help examples do.
code, comments, help examples, tests, or commit messages. Every placeholder address and URL uses a domain
reserved by [RFC 2606](https://www.rfc-editor.org/rfc/rfc2606): `example.com`, `example.net`, or
`example.org`. None of them can ever be registered, so a placeholder copied out of a help example and run
unedited, or a test whose mock is missing, cannot reach a real recipient. Prefer `example.com` for anything
new, but all three are equally correct: never rename existing placeholders to match that preference.
- **Never commit a real configuration file.** Configuration files hold the tenant ID, client ID, certificate
details, and encrypted secrets. `Tests/Config/` and `@Local Only/` are gitignored for that reason, and
`Templates/config_scriptmessage.json` must only ever contain placeholder values.
Expand Down Expand Up @@ -178,28 +181,15 @@ without going to look.
it and corrupts it.

Git will not show you a line-ending mistake: it normalizes before diffing, so a wrong file produces no diff
and leaves `git status` clean. Use this instead, which wants `w/lf` on every row:
and leaves `git status` clean. **`Tests/FileEncoding.Tests.ps1` enforces all of these rules** as part of the
test suite: no byte order mark (a module manifest that holds a non-ASCII character is the one exception), LF line
endings, and only ASCII in PowerShell files. It checks untracked files that are not ignored as well as tracked
ones, so it catches a new file before it is committed, and it skips any file containing a NUL byte. A failure
names each offending file.

```powershell
git ls-files --eol -- '*.ps1' '*.psm1' '*.psd1' '*.md' '*.json' '*.yml'
```

Nothing enforces the BOM and ASCII rules automatically yet, so check them before committing. Run this from the
repository root; it prints nothing when every tracked file passes. A module manifest saved with a BOM on purpose
is reported, and is the one expected exception:

```powershell
git ls-files | ForEach-Object {
$Path = Join-Path $PWD.ProviderPath $_ # .NET does not follow PowerShell's current location
$Bytes = [System.IO.File]::ReadAllBytes($Path)
if ($Bytes.Length -ge 3 -and $Bytes[0] -eq 0xEF -and $Bytes[1] -eq 0xBB -and $Bytes[2] -eq 0xBF) { "BOM: $_" }
if (($_ -match '\.(ps1|psm1|psd1|ps1xml)$') -and ([System.IO.File]::ReadAllText($Path) -match '[^\x00-\x7F]')) { "Non-ASCII: $_" }
}
```

New files can pick up CRLF on Windows (VS Code's default line ending follows the operating system), so check
a file you created with the `git ls-files --eol` command above once it is tracked. Never change the line
endings of a file you are not otherwise editing. To fix one that is wrong:
New files can pick up CRLF on Windows (VS Code's default line ending follows the operating system), so run the
tests after creating a file, not only after changing code. Never change the line endings of a file you are not
otherwise editing. To fix one that is wrong:

```powershell
$Path = (Resolve-Path -LiteralPath '<file>').ProviderPath # .NET needs a full path
Expand All @@ -215,6 +205,29 @@ $Text = [System.IO.File]::ReadAllText($Path)
`AliasesToExport`.
- **Comments describe the code as it is now**, never what it used to do or what a fix changed. Change history
belongs in the commit message and the changelog.
- **A boolean configuration setting is an opt-in flag: on only when it equals `$true`.** Read it as
`$ServiceConfig.<Name> -eq $true`, with the setting on the left, so missing, `false`, quoted text, and typos
all mean off. Never read one with a `[bool]` cast, a truthiness test, or `$true -eq`: each of those reads the
text `"false"` as true. Name a new boolean setting so that `true` opts in and off is the safe state.
- **A service that cannot honor a setting or parameter it receives says so in its result and sends what it
can.** Add a `Warning` entry to that message's `Error` property, as the Microsoft Graph service does for a
chat sent on behalf of someone else, rather than failing the whole call or dropping the value silently.
`Send-ScriptMessage` passes every message parameter to every service, so each service's send function must
accept all of them.

## Testing

- **Run `.\Tests\Invoke-Tests.ps1` from the repository root before proposing a commit.** It runs every
`Tests/*.Tests.ps1` file under both Windows PowerShell 5.1 and PowerShell 7, each in a fresh process, and the
CI workflow runs the same script. `-Edition Core` runs PowerShell 7 alone for a quicker check; finish with both,
because the editions behave differently in places.
- **The Pester files never connect to a tenant** and need no Microsoft Graph modules. Mock the service functions
such as `Send-ScriptMessage_MicrosoftGraph`, or the Microsoft Graph cmdlets they call. Pester can only mock a
command that exists, so a test that mocks a Graph cmdlet first defines a stand-in for it inside the module that
throws if reached, as the existing files do.
- **The other scripts in `Tests/` are not tests.** `ScriptMessage_Testing.ps1` and
`ScriptMessage_Testing (Installed Module).ps1` are scratch scripts that load the real configuration from
`Tests/Config/`, so anything run from them sends real messages; the safety rules above apply.

## Research notes

Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@

## [Unreleased](https://github.com/Sekers/ScriptMessage/compare/1.1.1...develop)

### Added

- New Parameter: `Send-ScriptMessage -MailType` > Chooses how an email with more than one recipient is sent. It overrides the configuration file's `MailType` setting, and `Group` is used when neither one sets it.
- `Group` sends one email to all of the To, CC, and BCC recipients.
- `OneOnOne` sends each To, CC, and BCC recipient a separate email with only that recipient in To, so no recipient can see who else received it. A recipient listed more than once gets one email.

### Fixed

- `Send-ScriptMessage` now honors an explicit `-ChatType OneOnOne`. The configuration file's `ChatType` setting was used in its place, so a call asking for one-on-one chats sent a single group chat when that setting was `Group`. An explicit `-ChatType Group` was unaffected.
- `Send-ScriptMessage` now honors an explicit `-IncludeBCCInGroupChat $false`. The configuration file's `IncludeBCCInGroupChat` setting was used in its place, so a call asking to keep BCC recipients out of a group chat added them anyway when that setting was `true`, making those addresses visible to everyone in the chat. An explicit `-IncludeBCCInGroupChat $true` was unaffected.
- `Send-ScriptMessage` now sends mail without a `ChatType` setting in the configuration file. A Mail-only send failed without it, reporting "Cannot convert null to type ChatType", even though `ChatType` only affects chat; a configuration file listing only `Mail` in `AllowableMessageTypes`, or one written before the setting was added in version 1.0.8, was affected. A `Chat` send with no chat type, because the setting is missing or blank and `-ChatType` was not passed, now reports that in the returned result's `Error` property and names both places it can be set.
- `Get-ScriptMessageContext -ReturnCachedContext` no longer reports a connection that `Disconnect-ScriptMessage` has already ended. Disconnecting left the cached context in place, so a cached read kept returning the account and scopes of the finished session, and only a read without `-ReturnCachedContext` showed the truth. Disconnecting now clears that service's cached context, so the next cached read asks the service again.
- Minor: Improved how `Send-ScriptMessage` validates configuration file settings, so a value in a form a setting does not support is no longer misread.
- Minor: The help for `Get-ScriptMessageConfig` and `Set-ScriptMessageConfigFilePath` now describes the `-Path` parameter, which it left blank.
- Minor: The fourth `Send-ScriptMessage` help example, which sends attachments held in variables, now runs as written. It failed with "Missing closing ')' in subexpression" when copied, and it ended without calling `Send-ScriptMessage`.

Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ Thank you for helping improve ScriptMessage. This guide covers the everyday work
## Rules

- **Never commit a real configuration file, secret, or certificate.** Never name a real organization, tenant, or
email domain in code, tests, examples, or commit messages; use `domain.com` addresses instead.
email domain in code, tests, examples, or commit messages; use an RFC 2606 reserved domain instead, such as
`example.com`. Those can never be registered, so a placeholder that gets run unedited reaches nobody.
- **The automated tests never connect to a tenant.** If a change needs a live Microsoft Graph test, run it in
your own test tenant and describe what you tested in the pull request.
- **Text files are UTF-8 with LF line endings and no byte order mark, and PowerShell files contain only ASCII.**
Windows PowerShell 5.1 misreads non-ASCII characters in PowerShell files saved without a byte order mark.
`.gitattributes` handles line endings, and [AGENTS.md](./AGENTS.md) explains the rules and how to check them.
`.gitattributes` handles line endings, the test suite checks all three rules, and [AGENTS.md](./AGENTS.md)
explains them.
- **Rebase only your own branch, and only before it merges.** Never force-push `develop` or `main`.

AI assistants working in this repository follow [AGENTS.md](./AGENTS.md), which also holds the detailed rules
Expand Down
18 changes: 12 additions & 6 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ Rules for the shared branches:
exist on `develop`, so every later release pull request would conflict with it.
- **Never rebase or force-push `main` or `develop`.** Release tags cannot move, and rewriting shared history
breaks every contributor's clone. Rebase only your own feature branch, and only before it merges.
- **After every release or hotfix, merge `main` back into `develop`**, so `develop` contains the release commit.
- **After every release or hotfix, merge `main` back into `develop` with a merge commit, never a squash or a
rebase**, so `develop` contains the tagged release commit. A squash or a rebase copies `main`'s changes into
new commits instead, so `develop` never contains the tagged commit, and a later release pull request can
conflict with a hotfix that `develop` already holds under a different commit. `develop` also allows squash
merges, for consolidating a feature branch, and GitHub's merge button offers the method used last, so check it
before merging `main` back.
- **Stable release tags go only on `main`.** Prerelease tags may go on `develop`.

## Changelog
Expand Down Expand Up @@ -139,7 +144,7 @@ section.
3. Builds the package once and publishes it to the PowerShell Gallery, in the `psgallery` environment.
4. Creates the GitHub release from the version's changelog section, attaches the same package, and
publishes the release.
6. **Merge `main` back into `develop`.**
6. **Merge `main` back into `develop` with a merge commit**, not a squash ([Branches](#branches) explains why).

The GitHub release is created last so that a failed check or publish never leaves behind a public release, which
immutable releases would lock, for a version that did not ship.
Expand All @@ -158,8 +163,8 @@ immutable releases would lock, for a version that did not ship.
2. Fix the problem, add tests, bump the patch version, and add a dated changelog section for the hotfix.
3. Open a pull request into `main`, merge it with a merge commit, and tag and push as in
[Releasing a version](#releasing-a-version), steps 4 and 5.
4. Merge `main` into `develop`. In `CHANGELOG.md`, keep the `[Unreleased]` section from `develop` at the top,
with the hotfix's section below it.
4. Merge `main` into `develop` with a merge commit. In `CHANGELOG.md`, keep the `[Unreleased]` section from
`develop` at the top, with the hotfix's section below it.

## Prereleases

Expand All @@ -174,7 +179,8 @@ A repository admin configures these once in the GitHub repository settings. They
enforceable:

- **Branch rulesets for `main` and `develop`:** require a pull request, require the `Workflow lint` and `Pester`
checks to pass, and block force pushes and deletion. For `main`, allow only merge commits.
checks to pass, and block force pushes and deletion. For `main`, allow only merge commits. For `develop`, also
allow squash merges, for consolidating a feature branch.
- **A tag ruleset for release tags:** target tags that look like versions, and block updates and deletion, with
bypass only for admins.
- **Immutable releases:** enabled, so a published release's tag and assets cannot change.
Expand All @@ -194,6 +200,6 @@ enforceable:
- [ ] The tag is annotated, has no `v` prefix, and is on the merge commit.
- [ ] Every job in the Release workflow passed.
- [ ] The version appears on the PowerShell Gallery, and the GitHub release has the right notes and package.
- [ ] `main` was merged back into `develop`.
- [ ] `main` was merged back into `develop` with a merge commit.
- [ ] Anything that needs a live Microsoft Graph test was tested separately in a test tenant. The automated tests
never connect to a tenant.
11 changes: 11 additions & 0 deletions Research_Notes/Microsoft-Graph-Behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ the module has no way to report a later delivery failure.
**Unverified:** that `Send-MgUserMail -PassThru` returns only `$true`. A comment in the module says so; it was
not checked.

**From source:** with `MailType` `Group`, `Send-ScriptMessage_MicrosoftGraph` calls `Send-MgUserMail` once, with
no `-ErrorAction`, inside a `try` whose `catch` puts the error in the result's `Error` property. With `OneOnOne`
it calls `Send-MgUserMail` once per unique recipient with `-ErrorAction Stop`, so each failure reaches that
recipient's own `catch` and the remaining recipients are still sent.

**Unverified:** whether a failed `Send-MgUserMail` raises a terminating error. The `Group` send depends on it: if
the error is non-terminating, it goes to the caller's error stream instead of `Error`, and `Status` is empty.

**Unverified:** how Exchange Online throttles many `sendMail` requests from one mailbox in quick succession,
which a `OneOnOne` send to a long recipient list makes. The sendMail page quoted above gives no figures.

## 4. From documentation: creating one-on-one and group chats

The Create chat page: "Only one one-on-one chat can exist between two members. If a one-on-one chat already
Expand Down
Loading
Loading