From 8417caeff08bdfcb0f510dc4e920b49261cd1b0e Mon Sep 17 00:00:00 2001 From: ThreeFive-O Date: Mon, 29 Jul 2019 14:50:56 +0200 Subject: [PATCH 1/4] Rework Send-MailMessage cmdlet with MailKit --- ...crosoft.PowerShell.Commands.Utility.csproj | 1 + .../commands/utility/Send-MailMessage.cs | 325 ++++++++++-------- .../Send-MailMessage.Tests.ps1 | 23 +- 3 files changed, 180 insertions(+), 169 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index 360e924773b..f976d425b73 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs index 5e91d0fdc43..7cad3ba099b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using MailKit; +using MimeKit; using System; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation; -using System.Net.Mail; using System.Text; namespace Microsoft.PowerShell.Commands @@ -14,7 +14,6 @@ namespace Microsoft.PowerShell.Commands /// /// Implementation for the Send-MailMessage command. /// - [Obsolete("This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage at this time. See https://aka.ms/SendMailMessage for more information.")] [Cmdlet(VerbsCommunications.Send, "MailMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135256")] public sealed class SendMailMessage : PSCmdlet { @@ -28,7 +27,6 @@ public sealed class SendMailMessage : PSCmdlet [Parameter(ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [Alias("PsPath")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Attachments { get; set; } /// @@ -37,7 +35,6 @@ public sealed class SendMailMessage : PSCmdlet /// [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Bcc { get; set; } /// @@ -71,8 +68,6 @@ public sealed class SendMailMessage : PSCmdlet /// [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Cc")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Cc { get; set; } /// @@ -82,7 +77,7 @@ public sealed class SendMailMessage : PSCmdlet [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("DNO")] [ValidateNotNullOrEmpty] - public DeliveryNotificationOptions DeliveryNotificationOption { get; set; } + public DeliveryStatusNotification DeliveryNotificationOption { get; set; } /// /// Gets or sets the from address for this e-mail message. The default value for @@ -107,7 +102,7 @@ public sealed class SendMailMessage : PSCmdlet /// [Parameter(ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - public MailPriority Priority { get; set; } + public MessagePriority Priority { get; set; } = MessagePriority.Normal; /// /// Gets or sets the Reply-To field for this e-mail message. @@ -127,7 +122,6 @@ public sealed class SendMailMessage : PSCmdlet /// [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] To { get; set; } /// @@ -154,58 +148,76 @@ public sealed class SendMailMessage : PSCmdlet [ValidateRange(0, Int32.MaxValue)] public int Port { get; set; } + /// + /// Specifies the priority of the mail message. + /// + /// + /// Backward compability matching with System.Net.Mail.MailPriority Enum. + /// + public enum MessagePriority + { + /// + /// The email has low priority. + /// + Low, + + /// + /// The email has normal priority. + /// + Normal, + + /// + /// The email has high priority. + /// + High + } + #endregion #region Private variables and methods - // Instantiate a new instance of MailMessage - private MailMessage _mMailMessage = new MailMessage(); + private class DsnSmtpClient : MailKit.Net.Smtp.SmtpClient + { + public DeliveryStatusNotification DeliveryStatusNotification { get; set; } + + protected override DeliveryStatusNotification? GetDeliveryStatusNotifications (MimeMessage message, MailboxAddress mailbox) + { + return DeliveryStatusNotification; + } + } + + private DsnSmtpClient _mSmtpClient = null; - private SmtpClient _mSmtpClient = null; + private PSVariable _mGlobalEmailServer = null; /// - /// Add the input addresses which are either string or hashtable to the MailMessage. - /// It returns true if the from parameter has more than one value. + /// Add the input address to the MimeMessage list. /// + /// /// - /// - private void AddAddressesToMailMessage(object address, string param) + private void AddMailAddress(InternetAddressList list, string address) { - string[] objEmailAddresses = address as string[]; - foreach (string strEmailAddress in objEmailAddresses) + try { - try - { - switch (param) - { - case "to": - { - _mMailMessage.To.Add(new MailAddress(strEmailAddress)); - break; - } - case "cc": - { - _mMailMessage.CC.Add(new MailAddress(strEmailAddress)); - break; - } - case "bcc": - { - _mMailMessage.Bcc.Add(new MailAddress(strEmailAddress)); - break; - } - case "replyTo": - { - _mMailMessage.ReplyToList.Add(new MailAddress(strEmailAddress)); - break; - } - } - } - catch (FormatException e) - { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, null); - WriteError(er); - continue; - } + list.Add(MailboxAddress.Parse(new MimeKit.ParserOptions(){ AddressParserComplianceMode = RfcComplianceMode.Strict, AllowAddressesWithoutDomain = false }, address)); + } + catch (MimeKit.ParseException ex) + { + ErrorRecord er = new ErrorRecord(ex, "FormatException", ErrorCategory.InvalidArgument, null); // Keep FormatException for error record for backward compability + WriteError(er); + } + } + + /// + /// Add the input addresses to the MimeMessage list. + /// + /// + /// + private void AddMailAddresses(InternetAddressList list, string[] addresses) + { + foreach(var strEmailAddress in addresses) + { + AddMailAddress(list, strEmailAddress); } } @@ -218,165 +230,180 @@ private void AddAddressesToMailMessage(object address, string param) /// protected override void BeginProcessing() { - try - { - // Set the sender address of the mail message - _mMailMessage.From = new MailAddress(From); - } - catch (FormatException e) - { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, From); - ThrowTerminatingError(er); - } + _mSmtpClient = new DsnSmtpClient(); - // Set the recipient address of the mail message - AddAddressesToMailMessage(To, "to"); + // Get the PowerShell environment variable + // PSEmailServer might be null if it is deleted by: PS> del variable:PSEmailServer + _mGlobalEmailServer = SessionState.Internal.GetVariable(SpecialVariables.PSEmailServer); + } - // Set the BCC address of the mail message - if (Bcc != null) + /// + /// ProcessRecord override. + /// + protected override void ProcessRecord() + { + // Fallback to global email server if SmtpServer parameter is not set + if (SmtpServer == null && _mGlobalEmailServer != null) { - AddAddressesToMailMessage(Bcc, "bcc"); + SmtpServer = Convert.ToString(_mGlobalEmailServer.Value, CultureInfo.InvariantCulture); } - // Set the CC address of the mail message - if (Cc != null) + if (string.IsNullOrEmpty(SmtpServer)) { - AddAddressesToMailMessage(Cc, "cc"); + ErrorRecord er = new ErrorRecord(new InvalidOperationException(SendMailMessageStrings.HostNameValue), null, ErrorCategory.InvalidArgument, null); + return; } - // Set the Reply-To address of the mail message - if (ReplyTo != null) + // Set default port for protocol + if (Port == 0) { - AddAddressesToMailMessage(ReplyTo, "replyTo"); + if(UseSsl) + { + Port = 465; // Standard SMTPS port + } + else + { + Port = 25; // Standard SMTP port + } } - // Set the delivery notification - _mMailMessage.DeliveryNotificationOptions = DeliveryNotificationOption; - - // Set the subject of the mail message - _mMailMessage.Subject = Subject; - - // Set the body of the mail message - _mMailMessage.Body = Body; - - // Set the subject and body encoding - _mMailMessage.SubjectEncoding = Encoding; - _mMailMessage.BodyEncoding = Encoding; + // Create mail message + var msg = new MimeMessage(); - // Set the format of the mail message body as HTML - _mMailMessage.IsBodyHtml = BodyAsHtml; + // Set the sender address of the mail message + AddMailAddress(msg.From, From); - // Set the priority of the mail message to normal - _mMailMessage.Priority = Priority; + // Set the recipient addresses of the mail message + AddMailAddresses(msg.To, To); - // Get the PowerShell environment variable - // globalEmailServer might be null if it is deleted by: PS> del variable:PSEmailServer - PSVariable globalEmailServer = SessionState.Internal.GetVariable(SpecialVariables.PSEmailServer); - - if (SmtpServer == null && globalEmailServer != null) + // Set the CC addresses of the mail message + if (Cc != null) { - SmtpServer = Convert.ToString(globalEmailServer.Value, CultureInfo.InvariantCulture); + AddMailAddresses(msg.Cc, Cc); } - if (string.IsNullOrEmpty(SmtpServer)) + // Set the BCC addresses of the mail message + if (Bcc != null) { - ErrorRecord er = new ErrorRecord(new InvalidOperationException(SendMailMessageStrings.HostNameValue), null, ErrorCategory.InvalidArgument, null); - this.ThrowTerminatingError(er); + AddMailAddresses(msg.Bcc, Bcc); } - if (Port == 0) - { - _mSmtpClient = new SmtpClient(SmtpServer); - } - else + // Set the Reply-To addresses of the mail message + if (ReplyTo != null) { - _mSmtpClient = new SmtpClient(SmtpServer, Port); + AddMailAddresses(msg.ReplyTo, ReplyTo); } - if (UseSsl) + // Set the subject of the mail message + if (Subject != null) { - _mSmtpClient.EnableSsl = true; + msg.Subject = Subject; } - if (Credential != null) + // Set the priority of the mail message + msg.Priority = (MimeKit.MessagePriority)Priority; + + // Create body + var builder = new BodyBuilder(); + + if (BodyAsHtml) { - _mSmtpClient.UseDefaultCredentials = false; - _mSmtpClient.Credentials = Credential.GetNetworkCredential(); + builder.HtmlBody = Body; } - else if (!UseSsl) + else { - _mSmtpClient.UseDefaultCredentials = true; + builder.TextBody = Body; } - } - /// - /// ProcessRecord override. - /// - protected override void ProcessRecord() - { // Add the attachments if (Attachments != null) { - string filepath = string.Empty; foreach (string attachFile in Attachments) { try { - filepath = PathUtils.ResolveFilePath(attachFile, this); + builder.Attachments.Add(attachFile); } - catch (ItemNotFoundException e) + catch (ArgumentException ex) { - // NOTE: This will throw - PathUtils.ReportFileOpenFailure(this, filepath, e); + ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, builder); + ThrowTerminatingError(er); + } + catch (UnauthorizedAccessException ex) + { + ErrorRecord er = new ErrorRecord(ex, "UnauthorizedAccessException", ErrorCategory.PermissionDenied, builder); + ThrowTerminatingError(er); + } + catch (System.IO.DirectoryNotFoundException ex) + { + ErrorRecord er = new ErrorRecord(ex, "DirectoryNotFoundException", ErrorCategory.InvalidArgument, builder); + ThrowTerminatingError(er); + } + catch (System.IO.FileNotFoundException ex) + { + ErrorRecord er = new ErrorRecord(ex, "FileNotFoundException", ErrorCategory.ObjectNotFound, builder); + ThrowTerminatingError(er); + } + catch (System.IO.IOException ex) + { + ErrorRecord er = new ErrorRecord(ex, "IOException", ErrorCategory.ReadError, builder); + ThrowTerminatingError(er); } - - Attachment mailAttachment = new Attachment(filepath); - _mMailMessage.Attachments.Add(mailAttachment); } } - } - /// - /// EndProcessing override. - /// - protected override void EndProcessing() - { + // Set the body of the mail message + msg.Body = builder.ToMessageBody(); + try { + // Connect to SMTP server + _mSmtpClient.Connect (SmtpServer, Port, UseSsl); + + // Authenticate if credentials are provided + if (Credential != null) + { + _mSmtpClient.Authenticate(Credential.GetNetworkCredential()); + } + + // Set the delivery notification + _mSmtpClient.DeliveryStatusNotification = DeliveryNotificationOption; + // Send the mail message - _mSmtpClient.Send(_mMailMessage); + _mSmtpClient.Send(msg); } - catch (SmtpFailedRecipientsException ex) + catch (MailKit.ProtocolException ex) { - ErrorRecord er = new ErrorRecord(ex, "SmtpFailedRecipientsException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "ProtocolException", ErrorCategory.ProtocolError, _mSmtpClient); WriteError(er); } - catch (SmtpException ex) + catch (MailKit.Security.AuthenticationException ex) { - if (ex.InnerException != null) - { - ErrorRecord er = new ErrorRecord(new SmtpException(ex.InnerException.Message), "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); - WriteError(er); - } - else - { - ErrorRecord er = new ErrorRecord(ex, "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); - WriteError(er); - } + ErrorRecord er = new ErrorRecord(ex, "AuthenticationException", ErrorCategory.AuthenticationError, _mSmtpClient); + WriteError(er); } catch (InvalidOperationException ex) { ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } - catch (System.Security.Authentication.AuthenticationException ex) + catch (ArgumentNullException ex) { - ErrorRecord er = new ErrorRecord(ex, "AuthenticationException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "ArgumentNullException", ErrorCategory.InvalidArgument, _mSmtpClient); WriteError(er); } + finally + { + _mSmtpClient.Disconnect(true); + } + } - // If we don't dispose the attachments, the sender can't modify or use the files sent. - _mMailMessage.Attachments.Dispose(); + /// + /// EndProcessing override. + /// + protected override void EndProcessing() + { + _mSmtpClient?.Dispose(); } #endregion diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 index cb05eabb8ba..05956265f67 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 @@ -109,21 +109,6 @@ Describe "Send-MailMessage DRT Unit Tests" -Tags CI, RequireSudoOnUnix { } ) - It "Shows obsolete message for cmdlet" { - $server | Should -Not -Be $null - - $powershell = [PowerShell]::Create() - - $null = $powershell.AddCommand("Send-MailMessage").AddParameters($testCases[0].InputObject).AddParameter("ErrorAction","SilentlyContinue") - - $powershell.Invoke() - - $warnings = $powershell.Streams.Warning - - $warnings.count | Should -BeGreaterThan 0 - $warnings[0].ToString() | Should -BeLike "The command 'Send-MailMessage' is obsolete. *" - } - It "Can send mail message using named parameters " -TestCases $testCases { param($InputObject) @@ -287,8 +272,8 @@ Describe "Send-MailMessage Feature Tests" -Tags Feature, RequireSudoOnUnix { } It "Can send mail with attachments" { - $attachment1 = "TestDrive:\attachment1.txt" - $attachment2 = "TestDrive:\attachment2.txt" + $attachment1 = "$TestDrive\attachment1.txt" + $attachment2 = "$TestDrive\attachment2.png" $pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAnElEQVR42u3RAQ0AAAgDoL9/aK3hHFSgyUw4o0KEIEQIQoQgRAhChAgRghAhCBGCECEIEYIQhAhBiBCECEGIEIQgRAhChCBECEKEIAQhQhAiBCFCECIEIQgRghAhCBGCECEIQYgQhAhBiBCECEEIQoQgRAhChCBECEIQIgQhQhAiBCFCEIIQIQgRghAhCBGCECFChCBECEKEIOS7BU5Hx50BmcQaAAAAAElFTkSuQmCC" @@ -302,9 +287,7 @@ Describe "Send-MailMessage Feature Tests" -Tags Feature, RequireSudoOnUnix { $mail = Read-Mail $mail.MessageParts.Count | Should -BeExactly 3 - $txt = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($mail.MessageParts[1].BodyData)) -replace "`n|`r" - $txt | Should -BeExactly "First attachment" - + ($mail.MessageParts[1].BodyData) | Should -BeExactly "First attachment" ($mail.MessageParts[2].BodyData -replace "`n|`r") | Should -BeExactly $pngBase64 } } From 82a1f8d8547a5e4f9ed8a20b68463cefae3c9122 Mon Sep 17 00:00:00 2001 From: ThreeFive-O Date: Mon, 29 Jul 2019 16:33:03 +0200 Subject: [PATCH 2/4] Fix code styles --- .../commands/utility/Send-MailMessage.cs | 55 +++++++++---------- .../Send-MailMessage.Tests.ps1 | 4 +- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs index 7cad3ba099b..a2a3a998ccc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using MailKit; -using MimeKit; using System; using System.Globalization; using System.Management.Automation; using System.Text; +using MailKit; +using MimeKit; namespace Microsoft.PowerShell.Commands { @@ -180,26 +180,26 @@ private class DsnSmtpClient : MailKit.Net.Smtp.SmtpClient { public DeliveryStatusNotification DeliveryStatusNotification { get; set; } - protected override DeliveryStatusNotification? GetDeliveryStatusNotifications (MimeMessage message, MailboxAddress mailbox) + protected override DeliveryStatusNotification? GetDeliveryStatusNotifications(MimeMessage message, MailboxAddress mailbox) { return DeliveryStatusNotification; } } - private DsnSmtpClient _mSmtpClient = null; + private DsnSmtpClient _smtpClient; - private PSVariable _mGlobalEmailServer = null; + private PSVariable _globalEmailServer; /// /// Add the input address to the MimeMessage list. /// - /// - /// + /// MimeMessage InternetAddressList property to which single address is added. + /// String with unparsed mailbox addresses. private void AddMailAddress(InternetAddressList list, string address) { try { - list.Add(MailboxAddress.Parse(new MimeKit.ParserOptions(){ AddressParserComplianceMode = RfcComplianceMode.Strict, AllowAddressesWithoutDomain = false }, address)); + list.Add(MailboxAddress.Parse(new MimeKit.ParserOptions() { AddressParserComplianceMode = RfcComplianceMode.Strict, AllowAddressesWithoutDomain = false }, address)); } catch (MimeKit.ParseException ex) { @@ -211,11 +211,11 @@ private void AddMailAddress(InternetAddressList list, string address) /// /// Add the input addresses to the MimeMessage list. /// - /// - /// + /// MimeMessage InternetAddressList property to which addresses are added. + /// String array with unparsed mailbox addresses. private void AddMailAddresses(InternetAddressList list, string[] addresses) { - foreach(var strEmailAddress in addresses) + foreach (var strEmailAddress in addresses) { AddMailAddress(list, strEmailAddress); } @@ -230,11 +230,11 @@ private void AddMailAddresses(InternetAddressList list, string[] addresses) /// protected override void BeginProcessing() { - _mSmtpClient = new DsnSmtpClient(); + _smtpClient = new DsnSmtpClient(); // Get the PowerShell environment variable // PSEmailServer might be null if it is deleted by: PS> del variable:PSEmailServer - _mGlobalEmailServer = SessionState.Internal.GetVariable(SpecialVariables.PSEmailServer); + _globalEmailServer = SessionState.Internal.GetVariable(SpecialVariables.PSEmailServer); } /// @@ -243,21 +243,18 @@ protected override void BeginProcessing() protected override void ProcessRecord() { // Fallback to global email server if SmtpServer parameter is not set - if (SmtpServer == null && _mGlobalEmailServer != null) - { - SmtpServer = Convert.ToString(_mGlobalEmailServer.Value, CultureInfo.InvariantCulture); - } + SmtpServer = SmtpServer ?? Convert.ToString(_globalEmailServer?.Value, CultureInfo.InvariantCulture); if (string.IsNullOrEmpty(SmtpServer)) { ErrorRecord er = new ErrorRecord(new InvalidOperationException(SendMailMessageStrings.HostNameValue), null, ErrorCategory.InvalidArgument, null); - return; + ThrowTerminatingError(er); } // Set default port for protocol if (Port == 0) { - if(UseSsl) + if (UseSsl) { Port = 465; // Standard SMTPS port } @@ -358,43 +355,43 @@ protected override void ProcessRecord() try { // Connect to SMTP server - _mSmtpClient.Connect (SmtpServer, Port, UseSsl); + _smtpClient.Connect(SmtpServer, Port, UseSsl); // Authenticate if credentials are provided if (Credential != null) { - _mSmtpClient.Authenticate(Credential.GetNetworkCredential()); + _smtpClient.Authenticate(Credential.GetNetworkCredential()); } // Set the delivery notification - _mSmtpClient.DeliveryStatusNotification = DeliveryNotificationOption; + _smtpClient.DeliveryStatusNotification = DeliveryNotificationOption; // Send the mail message - _mSmtpClient.Send(msg); + _smtpClient.Send(msg); } catch (MailKit.ProtocolException ex) { - ErrorRecord er = new ErrorRecord(ex, "ProtocolException", ErrorCategory.ProtocolError, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "ProtocolException", ErrorCategory.ProtocolError, _smtpClient); WriteError(er); } catch (MailKit.Security.AuthenticationException ex) { - ErrorRecord er = new ErrorRecord(ex, "AuthenticationException", ErrorCategory.AuthenticationError, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "AuthenticationException", ErrorCategory.AuthenticationError, _smtpClient); WriteError(er); } catch (InvalidOperationException ex) { - ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _smtpClient); WriteError(er); } catch (ArgumentNullException ex) { - ErrorRecord er = new ErrorRecord(ex, "ArgumentNullException", ErrorCategory.InvalidArgument, _mSmtpClient); + ErrorRecord er = new ErrorRecord(ex, "ArgumentNullException", ErrorCategory.InvalidArgument, _smtpClient); WriteError(er); } finally { - _mSmtpClient.Disconnect(true); + _smtpClient.Disconnect(true); } } @@ -403,7 +400,7 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - _mSmtpClient?.Dispose(); + _smtpClient?.Dispose(); } #endregion diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 index 05956265f67..4babf82f972 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 @@ -272,8 +272,8 @@ Describe "Send-MailMessage Feature Tests" -Tags Feature, RequireSudoOnUnix { } It "Can send mail with attachments" { - $attachment1 = "$TestDrive\attachment1.txt" - $attachment2 = "$TestDrive\attachment2.png" + $attachment1 = "$TestDrive/attachment1.txt" + $attachment2 = "$TestDrive/attachment2.png" $pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAnElEQVR42u3RAQ0AAAgDoL9/aK3hHFSgyUw4o0KEIEQIQoQgRAhChAgRghAhCBGCECEIEYIQhAhBiBCECEGIEIQgRAhChCBECEKEIAQhQhAiBCFCECIEIQgRghAhCBGCECEIQYgQhAhBiBCECEEIQoQgRAhChCBECEIQIgQhQhAiBCFCEIIQIQgRghAhCBGCECFChCBECEKEIOS7BU5Hx50BmcQaAAAAAElFTkSuQmCC" From d7ae6f17a5e2150627eaaff45c5dcb91aa5ba253 Mon Sep 17 00:00:00 2001 From: ThreeFive-O Date: Wed, 31 Jul 2019 09:58:43 +0200 Subject: [PATCH 3/4] Fix files.wxs with new dependencies --- assets/files.wxs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/assets/files.wxs b/assets/files.wxs index 14c8a7c01ae..4258c0df85c 100644 --- a/assets/files.wxs +++ b/assets/files.wxs @@ -3042,6 +3042,15 @@ + + + + + + + + + @@ -3874,6 +3883,9 @@ + + + From 93c4adc891109fbc7ec12b73d60c12c337ed1933 Mon Sep 17 00:00:00 2001 From: ThreeFive-O Date: Thu, 1 Aug 2019 10:20:23 +0200 Subject: [PATCH 4/4] Fix pipelining #7591 --- .../commands/utility/Send-MailMessage.cs | 40 +++++++++++++++---- .../Send-MailMessage.Tests.ps1 | 4 +- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs index a2a3a998ccc..0184a10395a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs @@ -199,7 +199,7 @@ private void AddMailAddress(InternetAddressList list, string address) { try { - list.Add(MailboxAddress.Parse(new MimeKit.ParserOptions() { AddressParserComplianceMode = RfcComplianceMode.Strict, AllowAddressesWithoutDomain = false }, address)); + list.Add(MailboxAddress.Parse(new MimeKit.ParserOptions { AddressParserComplianceMode = RfcComplianceMode.Strict, AllowAddressesWithoutDomain = false }, address)); } catch (MimeKit.ParseException ex) { @@ -313,7 +313,23 @@ protected override void ProcessRecord() } // Add the attachments - if (Attachments != null) + /* Note for second check: + * The solution below is a workaround to check if the Attachments parameter is null for the PSCustomObject piped into the cmdlet + * and therefore the Attachments parameter is falsely set to the casted PSCustomObject. + * + * Attachments parameter is not mandatory but declared as ValueFromPipeline and ValueFromPipelineByPropertyName. + * If PSCustomObject is piped into cmdlet and Attachments property is not present (ValueFromPipelineByPropertyName), + * than the binding process will try to set Attachments to the piped PSCustomObject (ValueFromPipeline). + * + * Problem: PSCustomObject (Pipeline input) can be casted to string[] (Attachments parameter) + * Attachments will hold at least one string with the current pipeline object as a string. E.g. "@{SmtpServer=localhost, From=foo@contonso.com, To=bar@contonso.com}" + * + * A simple check if Attachments starts with "@{" or even a pattern match might lead to problems, because the file name for the + * attachment could THEORETICALLY be "@{SmtpServer=localhost, From=foo@contonso.com, To=bar@contonso.com}" without any extension. + * + * The problem only occurs for parameters which are not mandatory but have [ValueFromPipeline] and [ValueFromPipelineByPropertyName] attribute. + */ + if (Attachments != null && Attachments?[0] != CurrentPipelineObject.ToString()) { foreach (string attachFile in Attachments) { @@ -324,27 +340,27 @@ protected override void ProcessRecord() catch (ArgumentException ex) { ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, builder); - ThrowTerminatingError(er); + WriteError(er); } catch (UnauthorizedAccessException ex) { ErrorRecord er = new ErrorRecord(ex, "UnauthorizedAccessException", ErrorCategory.PermissionDenied, builder); - ThrowTerminatingError(er); + WriteError(er); } catch (System.IO.DirectoryNotFoundException ex) { ErrorRecord er = new ErrorRecord(ex, "DirectoryNotFoundException", ErrorCategory.InvalidArgument, builder); - ThrowTerminatingError(er); + WriteError(er); } catch (System.IO.FileNotFoundException ex) { ErrorRecord er = new ErrorRecord(ex, "FileNotFoundException", ErrorCategory.ObjectNotFound, builder); - ThrowTerminatingError(er); + WriteError(er); } catch (System.IO.IOException ex) { ErrorRecord er = new ErrorRecord(ex, "IOException", ErrorCategory.ReadError, builder); - ThrowTerminatingError(er); + WriteError(er); } } } @@ -384,6 +400,16 @@ protected override void ProcessRecord() ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _smtpClient); WriteError(er); } + catch (System.IO.IOException ex) + { + ErrorRecord er = new ErrorRecord(ex, "IOException", ErrorCategory.ReadError, builder); + WriteError(er); + } + catch (System.Net.Sockets.SocketException ex) + { + ErrorRecord er = new ErrorRecord(ex, "SocketException", ErrorCategory.ConnectionError, builder); + WriteError(er); + } catch (ArgumentNullException ex) { ErrorRecord er = new ErrorRecord(ex, "ArgumentNullException", ErrorCategory.InvalidArgument, _smtpClient); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 index 4babf82f972..c0e66fac5b1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Send-MailMessage.Tests.ps1 @@ -141,11 +141,9 @@ Describe "Send-MailMessage DRT Unit Tests" -Tags CI, RequireSudoOnUnix { $mail.MessageParts[0].BodyData | Should -BeExactly $InputObject.Body } - It "Can send mail message using pipline named parameters " -TestCases $testCases -Pending { + It "Can send mail message using pipline named parameters " -TestCases $testCases { param($InputObject) - Set-TestInconclusive "As of right now the Send-MailMessage cmdlet does not support piping named parameters (see issue 7591)" - $server | Should -Not -Be $null [PsCustomObject]$InputObject | Send-MailMessage -ErrorAction SilentlyContinue