-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add -Delimiter & -NoTrailingDelimiter parameters to Set-Content/Add-Content cmdlets
#24648
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add -Delimiter & -NoTrailingDelimiter parameters to Set-Content/Add-Content cmdlets
#24648
Conversation
|
@mklement0 Please review if you get a chance. I've added the delimiter parameters in, I will tackle the |
|
I'll leave the review to others, @ArmaanMcleod, but thank you for tackling this. |
|
Does this also apply to |
|
@JunielKatarn, please see #23906 (comment) |
|
Can the behavior of |
This breaks the streaming behavior. What if tons of strings are fed into the pipeline? Holding them up in a buffer could drastically increase the memory usage and might even cause an out-of-memory exception. |
This effectively is what ConvertFrom-Json does as well. |
|
But still, |
Yeah its a similar issue with this feature as well since we need to detect last item which requires loading all lines into buffer. I wonder though if we can just keep streaming behaviour and only buffer if user wants to use these flags, unless I am missing something and this can all be achieved with streaming? Might make command too complex for what its worth though, and still keeps performance issue around just to save some keystrokes and reduce pipelining. Happy to close PR if this is not desired 🙂. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds new -Delimiter and -NoTrailingDelimiter parameters to the Set-Content and Add-Content cmdlets, allowing users to specify custom delimiters when writing content to files. The implementation includes a significant architectural change: content is now buffered in ProcessRecord() and written in EndProcessing() to enable proper handling of the trailing delimiter suppression. The PR includes comprehensive test coverage, parameter validation, and appropriate error messages for invalid parameter combinations.
Key Changes:
- Added
-Delimiterand-NoTrailingDelimiterparameters to enable custom delimiter control when writing content - Refactored content writing to use a buffering approach, accumulating content during pipeline processing and writing once in
EndProcessing() - Added validation logic to prevent incompatible parameter combinations (Delimiter with NoNewLine/AsByteStream, NoTrailingDelimiter without Delimiter)
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
test/powershell/Modules/Microsoft.PowerShell.Management/Set-Content.Tests.ps1 |
Adds comprehensive test coverage for delimiter functionality including parameter validation, various delimiter types, and nested content handling |
test/powershell/Modules/Microsoft.PowerShell.Management/Add-Content.Tests.ps1 |
Adds comprehensive test coverage for delimiter functionality with append operations, and removes trailing whitespace |
src/System.Management.Automation/resources/FileSystemProviderStrings.resx |
Adds error message strings for invalid parameter combinations |
src/System.Management.Automation/namespaces/FileSystemProvider.cs |
Adds dynamic parameters (Delimiter, NoTrailingDelimiter) and validation logic to GetContentWriter method |
src/System.Management.Automation/namespaces/FileSystemContentStream.cs |
Adds new constructor overload for delimiter support and refactors Write method to handle delimiter insertion and trailing delimiter suppression |
src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs |
Refactors ProcessRecord to buffer content and EndProcessing to perform actual writing, enabling proper last-item detection for delimiter suppression |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
test/powershell/Modules/Microsoft.PowerShell.Management/Set-Content.Tests.ps1
Outdated
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Management/Add-Content.Tests.ps1
Outdated
Show resolved
Hide resolved
| string streamName = null; | ||
| bool suppressNewline = false; | ||
|
|
||
| string delimiter = "\n"; |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default delimiter value is hardcoded as "\n" here, but FileSystemContentReaderWriter uses a constant DefaultDelimiter = '\n'. Consider using a consistent approach, either by using the same constant or ensuring the default values match. This helps maintain consistency and makes future changes easier.
| string delimiter = "\n"; | |
| string delimiter = FileSystemContentReaderWriter.DefaultDelimiter; |
src/System.Management.Automation/resources/FileSystemProviderStrings.resx
Outdated
Show resolved
Hide resolved
src/System.Management.Automation/resources/FileSystemProviderStrings.resx
Outdated
Show resolved
Hide resolved
src/System.Management.Automation/namespaces/FileSystemContentStream.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs
Outdated
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Management/Add-Content.Tests.ps1
Outdated
Show resolved
Hide resolved
src/System.Management.Automation/namespaces/FileSystemProvider.cs
Outdated
Show resolved
Hide resolved
…iter needs to be suppressed
…ntent.Tests.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ntent.Tests.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…trings.resx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…trings.resx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tream.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ntent.Tests.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@daxian-dbw I think I found a good solution here. Instead of buffering all lines to handle last line trailing delimiter, we can maintain streaming behaviour if we just prepend delimiter after the first line if last delimiter needs to be suppressed with I am not sure why I didn't do this before, but I think there should be no performance issue now 🙂. |
| // Always append delimter if we don't suppress last delimiter | ||
| if (!_suppressLastDelimiter) | ||
| { | ||
| _writer.Write(_delimiter); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could assign _delimiter in Beginning as needed and write here unconditionally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept same _usingDelimiter and _delimiter flags and set them in new FileSystemContentReaderWriter constructor. Seemed much simpler to manage it this way.
Logic is simpler in f6378d3 now. Default should be to append delimiter unless we need to prepend after first line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologise above actually doesn't work, I have split the code paths using _suppressLastDelimiter in 6255eb3.
| /// Gets the status of the delimiter parameter. Returns true | ||
| /// if the delimiter was explicitly specified by the user, false otherwise. | ||
| /// </summary> | ||
| public bool DelimiterSpecified |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this. It seems we could simply check for default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Existing dynamic parameters code checked for this flag, I just maintained it as well for this to keep it consistent. I think it can be simplified but perhaps in another PR when both FileSystemContentWriterDynamicParameters and FileSystemContentReaderDynamicParameters is consolidated.
We also should also centralise the default delimiter "\n" in a better place, it seems it is also hardcoded in many places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


PR Summary
Add
-Delimiter&-NoTrailingDelimiterparameters toSet-Content/Add-Contentcmdlets.PR Context
Fixes #23906.
I've added new parameters to
Set-Content/Add-Contentso a user can specify their own delimiters using-Delimiterwhen writing to files.This included making changes toWriteContentCommandBaseclass to process all lines into a buffer inProcessRecord()instead of writing one at a time, and do all the writing inEndProcessing()so we can append delimiters but also make sure the trailing delimiter can be suppressed using-NoTrailingDelimiter. This seemed much simpler to do sinceValuecan be used with a pipeline which makes it difficult to detect the last line and suppress the final delimiter.Update from above
When
-NoTrailingDelimiteris specified, all delimiters are prepended to all lines except first line. Otherwise, we fall back to default behaviour and prepend delimiter after every line. This approach is more ideal than the previous buffering approach because streaming behaviour of input lines is maintained.I also needed to make changes to the
FileSystemProvidermethodIContentWriter GetContentWriter(string path)to handle new dynamic parameters inFileSystemContentWriterDynamicParametersfor delimiters and also include a new overload forFileSystemContentReaderWriter. There is also some error handling if we use parameter combinations which are not supported.Example 1
Write contents to file using delimiters:
Example 2
Write contents to file using comma delimiter and remove trailing delimiter:
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:or[ WIP ]to the beginning of the title (theWIPbot will keep its status check atPendingwhile the prefix is present) and remove the prefix when the PR is ready.- [x] Issue filed: Document
-Delimiter&-NoTrailingDelimiterparameters forSet-Content/Add-Contentcmdlets MicrosoftDocs/PowerShell-Docs#11567(which runs in a different PS Host).