Skip to content

Conversation

@DevanshG1
Copy link
Contributor

@DevanshG1 DevanshG1 commented Feb 9, 2026

Description

Mandatory Checklist

  • SHOULD update ChangeLog.md file(s) appropriately
    • Update src/{{SERVICE}}/{{SERVICE}}/ChangeLog.md.
      • A snippet outlining the change(s) made in the PR should be written under the ## Upcoming Release header in the past tense.
    • Should not change ChangeLog.md if no new release is required, such as fixing test case only.
  • SHOULD regenerate markdown help files if there is cmdlet API change. Instruction
  • SHOULD have proper test coverage for changes in pull request.
  • SHOULD NOT adjust version of module manually in pull request

Copilot AI review requested due to automatic review settings February 9, 2026 11:17
@azure-client-tools-bot-prd
Copy link

Thanks for your contribution! The pull request validation has started. Please revisit this comment for updated status.

Copy link
Contributor

Copilot AI left a 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 extends the Az.Sftp module to make Connect-AzSftp more configurable and diagnosable by adding new connection options and enriching debug/verbose output across SFTP cmdlets.

Changes:

  • Added -BufferSize (int) to control the sftp -B transfer buffer size.
  • Added -StorageAccountEndpoint to allow overriding the storage endpoint suffix used to build the SFTP hostname.
  • Expanded debug/verbose logging in Connect-AzSftp and New-AzSftpCertificate, and updated help + changelog + scenario tests accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Sftp/Sftp/help/Connect-AzSftp.md Documents new -BufferSize and -StorageAccountEndpoint parameters in syntax and parameter sections.
src/Sftp/Sftp/SftpCommands/ConnectAzSftpCommand.cs Adds the two new parameters, passes buffer size into session, supports custom endpoint suffix, and adds detailed logging/cleanup messaging.
src/Sftp/Sftp/Models/SFTPSession.cs Adds BufferSizeBytes and emits -B in the generated sftp command args (conditionally).
src/Sftp/Sftp/SftpCommands/NewAzSftpCertificateCommand.cs Adds more structured debug/verbose logging and clearer warnings/errors around certificate generation.
src/Sftp/Sftp/CHANGELOG.md Records the two new parameters and their intent under “Upcoming Release”.
src/Sftp/Sftp.Test/ScenarioTests/ConnectAzSftpTests.ps1 Adds new scenario test functions intended to exercise the new parameters.

Comment on lines 150 to 225
<#
.SYNOPSIS
Test Connect-AzSftp with BufferSize parameter
#>
function Test-ConnectAzSftpWithBufferSize
{
$storageAccountName = Get-StorageAccountName
$resourceGroupName = Get-ResourceGroupName

try {
# Skip test in playback mode for now
if (IsPlayback) {
return
}

# Create test storage account
$storageAccount = New-TestStorageAccount -ResourceGroupName $resourceGroupName -StorageAccountName $storageAccountName

# Test connection with custom buffer size (will fail in test environment but validates parameter parsing)
try {
$result = Connect-AzSftp -StorageAccount $storageAccountName -BufferSize 524288 -SftpArg "-o", "ConnectTimeout=1"
}
catch {
# Expected to fail in test environment - this is acceptable
Write-Host "Connection failed as expected in test environment: $($_.Exception.Message)"
}

# Test with default buffer size (256*1024 = 262144)
try {
$result = Connect-AzSftp -StorageAccount $storageAccountName -SftpArg "-o", "ConnectTimeout=1"
}
catch {
Write-Host "Connection failed as expected in test environment: $($_.Exception.Message)"
}
}
finally {
# Cleanup
Remove-TestStorageAccount -ResourceGroupName $resourceGroupName -StorageAccountName $storageAccountName
Remove-AzResourceGroup -Name $resourceGroupName -Force -ErrorAction SilentlyContinue
}
}

<#
.SYNOPSIS
Test Connect-AzSftp with StorageAccountEndpoint parameter
#>
function Test-ConnectAzSftpWithStorageAccountEndpoint
{
$storageAccountName = Get-StorageAccountName
$resourceGroupName = Get-ResourceGroupName
$customEndpoint = "blob.core.custom.endpoint.net"

try {
# Skip test in playback mode for now
if (IsPlayback) {
return
}

# Create test storage account
$storageAccount = New-TestStorageAccount -ResourceGroupName $resourceGroupName -StorageAccountName $storageAccountName

# Test connection with custom storage account endpoint (will fail in test environment but validates parameter parsing)
try {
$result = Connect-AzSftp -StorageAccount $storageAccountName -StorageAccountEndpoint $customEndpoint -SftpArg "-o", "ConnectTimeout=1"
}
catch {
# Expected to fail in test environment - this is acceptable
Write-Host "Connection failed as expected in test environment: $($_.Exception.Message)"
}
}
finally {
# Cleanup
Remove-TestStorageAccount -ResourceGroupName $resourceGroupName -StorageAccountName $storageAccountName
Remove-AzResourceGroup -Name $resourceGroupName -Force -ErrorAction SilentlyContinue
}
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added PowerShell scenario test functions don’t provide meaningful coverage in CI: the ConnectAzSftpTestsRunner has all [Fact] tests skipped, and these new functions also early-return in playback mode and only swallow connection failures without asserting on behavior (e.g., that -B is included in the generated sftp command, or that the custom endpoint affects the hostname). Add deterministic unit tests in the existing non-skipped C# test suite (e.g., SftpUtilsTests/SFTPSession tests) to validate -B argument emission and host construction, or otherwise ensure these tests are actually executed.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like work.
Life's Always got its own Earth Shadow.
Keep is safe.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like work.
Life's Always got its own Earth Shadow.
Keep is safe.

Comment on lines 226 to 229
if (BufferSizeBytes != 256 * 1024)
{
args.AddRange(new[] { "-B", BufferSizeBytes.ToString() });
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BufferSizeBytes is treated as if 256 KB is the default, but BuildArgs() only adds the -B flag when the value differs from 256*1024. As a result, when users do not pass -BufferSize (or pass 262144), the cmdlet won’t pass -B at all and the effective buffer size will fall back to the underlying sftp client’s default—contradicting the help/CHANGELOG that state a default of 262144. Decide on one behavior and make code+docs consistent (e.g., always pass -B with the default, or make buffer size optional/nullable and only add -B when the parameter was explicitly provided).

Suggested change
if (BufferSizeBytes != 256 * 1024)
{
args.AddRange(new[] { "-B", BufferSizeBytes.ToString() });
}
args.AddRange(new[] { "-B", BufferSizeBytes.ToString() });

Copilot uses AI. Check for mistakes.
Comment on lines +428 to 432
WriteDebug($"[SFTP] Waiting for SFTP session to complete before cleanup...");
process.WaitForExit();
// Wait up to 5 minutes (300,000 ms) for the process to exit
bool exited = process.WaitForExit(300000);
if (!exited)
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The process wait logic makes the 5-minute timeout ineffective: process.WaitForExit() blocks indefinitely before the timed WaitForExit(300000) is called. This can hang the cmdlet and prevent credential cleanup. Remove the unconditional wait and rely on the timed wait (or apply a single wait strategy) so the timeout actually works.

Copilot uses AI. Check for mistakes.
@amandalanise9-tech
Copy link

That's a Healthy Conversation,
on This Subject. In My Opinion.
Still Dangerous.
Better one day. :\

@DevanshG1
Copy link
Contributor Author

@isra-fel Please review this PR

@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@github-actions
Copy link

‼️ DO NOT MERGE THIS PR ‼️
This PR was labeled "Do Not Merge" because it contains code change that cannot be merged. Please contact the reviewer for more information.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 10, 2026 04:50
Copy link
Contributor

Copilot AI left a 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 6 out of 6 changed files in this pull request and generated 3 comments.

Comment on lines +529 to +535
throw new PSArgumentException(
"StorageAccountEndpoint must be a DNS suffix such as 'blob.core.windows.net' and must not contain a scheme or path.");
}

if (string.IsNullOrEmpty(normalizedSuffix))
{
throw new PSArgumentException("StorageAccountEndpoint cannot be empty.");
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PSArgumentException thrown for invalid StorageAccountEndpoint doesn’t specify the parameter name, which makes PowerShell error output less actionable (it won’t highlight which argument is invalid). Consider using the overload that includes paramName (or an AzPSArgumentException with nameof(StorageAccountEndpoint)) for clearer diagnostics.

Suggested change
throw new PSArgumentException(
"StorageAccountEndpoint must be a DNS suffix such as 'blob.core.windows.net' and must not contain a scheme or path.");
}
if (string.IsNullOrEmpty(normalizedSuffix))
{
throw new PSArgumentException("StorageAccountEndpoint cannot be empty.");
throw new AzPSArgumentException(
"StorageAccountEndpoint must be a DNS suffix such as 'blob.core.windows.net' and must not contain a scheme or path.",
nameof(StorageAccountEndpoint));
}
if (string.IsNullOrEmpty(normalizedSuffix))
{
throw new AzPSArgumentException("StorageAccountEndpoint cannot be empty.", nameof(StorageAccountEndpoint));

Copilot uses AI. Check for mistakes.
Comment on lines 158 to 160
### -BufferSize
Buffer size in bytes for SFTP file transfers. Default: 262144 (256 KB).

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text claims a default BufferSize of 262144 (256 KB), but the current implementation only adds the sftp -B flag when a non-default value is set. Unless -B 262144 is always emitted, the effective default will be whatever the sftp client uses. Please align the documentation with actual behavior (or update the code so the stated default is enforced).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest naming it -BufferSizeInBytes for usability

## Upcoming Release
* Added `BufferSize` parameter to `Connect-AzSftp` cmdlet
- Allows users to specify buffer size in bytes for SFTP file transfers using the sftp -B flag
- Default value is 262144 (256 KB)
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGELOG notes a BufferSize default of 262144 (256 KB), but the current sftp -B argument is only emitted when a non-default value is used. Either ensure the default is actually applied in the command invocation, or adjust the changelog entry to avoid stating an incorrect default behavior.

Suggested change
- Default value is 262144 (256 KB)
- When specified, a value of 262144 (256 KB) is recommended for optimal performance

Copilot uses AI. Check for mistakes.
@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@DevanshG1
Copy link
Contributor Author

@isra-fel please run the checks

Copy link
Member

@isra-fel isra-fel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the inline comments

Comment on lines 158 to 160
### -BufferSize
Buffer size in bytes for SFTP file transfers. Default: 262144 (256 KB).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest naming it -BufferSizeInBytes for usability

@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

@github-actions
Copy link

This PR was labeled "needs-revision" because it has unresolved review comments or CI failures.
Please resolve all open review comments and make sure all CI checks are green. Refer to our guide to troubleshoot common CI failures.

Copilot AI review requested due to automatic review settings February 10, 2026 06:51
@DevanshG1 DevanshG1 requested a review from isra-fel February 10, 2026 07:00
Copy link
Contributor

Copilot AI left a 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 9 out of 9 changed files in this pull request and generated 6 comments.

Comment on lines +390 to +405
// Arrange - default buffer size is 256 * 1024 = 262144
var session = new SFTPSession(
storageAccount: "testaccount",
username: "testuser",
host: "testaccount.blob.core.windows.net",
port: 22,
publicKeyFile: null,
privateKeyFile: null,
certFile: null,
sftpArgs: null,
sshClientFolder: null,
sshProxyFolder: null,
credentialsFolder: null,
yesWithoutPrompt: false,
bufferSizeBytes: 256 * 1024 // Default value
);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests hardcode the default buffer size as 256 * 1024 / 262144. Since the production default is defined in SftpConstants.DefaultBufferSizeBytes, using the constant here would keep tests aligned if the default ever changes and avoid duplicating magic numbers.

Copilot uses AI. Check for mistakes.
PublicKeyFile, PrivateKeyFile, credentialsFolder, SshClientFolder);
PublicKeyFile = pubKey;
PrivateKeyFile = privKey;

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileUtils.CheckOrCreatePublicPrivateFiles returns a deleteKeys flag (delKeys here) indicating whether key material was generated and should be cleaned up, but the value is never applied to the cmdlet-level deleteKeys. This can leave newly generated keys on disk unexpectedly (and they contain sensitive material). Propagate delKeys into deleteKeys (e.g., deleteKeys |= delKeys) so cleanup behavior matches what FileUtils reports.

Suggested change
deleteKeys |= delKeys;

Copilot uses AI. Check for mistakes.
Comment on lines +517 to +539
// Use custom endpoint suffix if provided
if (!string.IsNullOrWhiteSpace(StorageAccountEndpoint))
{
var normalizedSuffix = StorageAccountEndpoint.Trim();

// Remove any leading dots so that values like ".blob.core.windows.net" are accepted
normalizedSuffix = normalizedSuffix.TrimStart('.');

// Reject values that look like full URLs or contain paths, since we only expect a DNS suffix
if (normalizedSuffix.IndexOf("://", StringComparison.Ordinal) >= 0 ||
normalizedSuffix.IndexOf("/", StringComparison.Ordinal) >= 0)
{
throw new PSArgumentException(
"StorageAccountEndpoint must be a DNS suffix such as 'blob.core.windows.net' and must not contain a scheme or path.");
}

if (string.IsNullOrEmpty(normalizedSuffix))
{
throw new PSArgumentException("StorageAccountEndpoint cannot be empty.");
}

return normalizedSuffix;
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StorageAccountEndpoint introduces new parsing/validation behavior (trimming leading dots and rejecting schemes/paths), but there are no unit tests covering valid/invalid inputs or verifying the computed hostname uses the custom suffix. Add deterministic C# tests for GetStorageEndpointSuffix behavior (or refactor it into a testable helper) so regressions are caught without relying on skipped E2E scripts.

Copilot uses AI. Check for mistakes.
Comment on lines +265 to +269
// Test that BufferSizeInBytes has correct default value (256 * 1024 = 262144)
var connectCommand = new ConnectAzSftpCommand();

Assert.Equal(256 * 1024, connectCommand.BufferSizeInBytes);
Assert.Equal(262144, connectCommand.BufferSizeInBytes);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default-value test duplicates the production constant as 256 * 1024 and 262144. Prefer asserting against SftpConstants.DefaultBufferSizeBytes so tests remain correct if the default is ever adjusted, and you only have one source of truth.

Suggested change
// Test that BufferSizeInBytes has correct default value (256 * 1024 = 262144)
var connectCommand = new ConnectAzSftpCommand();
Assert.Equal(256 * 1024, connectCommand.BufferSizeInBytes);
Assert.Equal(262144, connectCommand.BufferSizeInBytes);
// Test that BufferSizeInBytes has correct default value
var connectCommand = new ConnectAzSftpCommand();
Assert.Equal(SftpConstants.DefaultBufferSizeBytes, connectCommand.BufferSizeInBytes);

Copilot uses AI. Check for mistakes.
return normalizedSuffix;
}

string cloudName = DefaultContext?.Environment?.Name?.ToLower() ?? "azurecloud";
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DefaultContext?.Environment?.Name?.ToLower() uses current-culture casing rules. For environment name comparisons, use ToLowerInvariant() to avoid culture-specific edge cases (e.g., Turkish-I).

Suggested change
string cloudName = DefaultContext?.Environment?.Name?.ToLower() ?? "azurecloud";
string cloudName = DefaultContext?.Environment?.Name?.ToLowerInvariant() ?? "azurecloud";

Copilot uses AI. Check for mistakes.
Comment on lines +525 to +531
// Reject values that look like full URLs or contain paths, since we only expect a DNS suffix
if (normalizedSuffix.IndexOf("://", StringComparison.Ordinal) >= 0 ||
normalizedSuffix.IndexOf("/", StringComparison.Ordinal) >= 0)
{
throw new PSArgumentException(
"StorageAccountEndpoint must be a DNS suffix such as 'blob.core.windows.net' and must not contain a scheme or path.");
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StorageAccountEndpoint is treated as a DNS suffix, but the current validation allows characters like :. In OpenSSH sftp, a destination of the form host:dir is parsed as an initial remote path, so allowing : in the suffix can change semantics unexpectedly. Consider rejecting : (and other non-DNS characters/whitespace) so the parameter is constrained to a true hostname suffix.

Copilot uses AI. Check for mistakes.
@isra-fel
Copy link
Member

/azp run

@azure-pipelines
Copy link
Contributor

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Member

@isra-fel isra-fel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@isra-fel isra-fel merged commit f8f5ee6 into Azure:main Feb 11, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants