Send-SHDMailMessage

Send-SHDMailMessage

Recently the send-mailmessage was put to rest with good reason. It failed to do its job by securing the emails. It sent items via plain text and not SSL encrypted. Great for internal nothing fancy, but bad if you wanted to send data outside the world. So, I built an alternative that I use the mail message system inside windows. Let’s Send Mail With Powershell.

The Script

function Send-SHDMailMessage {
    <#
    .SYNOPSIS
        Sends an email using custom SMTP settings.
    .DESCRIPTION
        Sends an email using custom SMTP settings.
    .PARAMETER From
        As string. Name of the Email address from which it will come from. 
        Example: "David Bolding <admin@example.com>"
    .PARAMETER To
        As string. The email address too. 
        Example: "Admin@example.com"
    .PARAMETER SMTPUsername
        As string. The Username for the SMTP service you will be using. 
    .PARAMETER SMTPPassword
        As string. The Password in plain text for the smtp service you will be using. 
    .PARAMETER SMTPServer
        As string. The server name of the SMTP service you will be using. 
    .PARAMETER SMTPPort
        As string. The Server Port for the smtp serivce you will be using. 
    .PARAMETER Subject
        As string. The subject line of the email. 
    .PARAMETER Body
        As string. The body of the email as a string. Body takes default over BodyAsHtml
    .PARAMETER BodyAsHTML
        As string. The body of the email in html format. 
    .PARAMETER PlainText
        Sends the email in plain text and not ssl. 
    .PARAMETER Attachment
        As array of strings. A list of full file names for attachments. Example: "c:\temp\log.log"
    .PARAMETER CC
        Email address for a carbon copy to this email.
    .PARAMETER BCC
        Email address for a blind carbon copy of this email. 
    .PARAMETER Priority
        A validate set for High, Normal, Low. By default it will send emails out with Normal.
    .EXAMPLE
        $Message = @{
            from = "HR <HumanResources@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "Test"
            Attachment = "C:\temp\JobOffer1.pdf","C:\temp\JobOffer2.pdf"
            BodyAsHtml = @"
            <html>
            <body>
                <center><h1>Congradulation</h1></center>
                <hr>
                <p>Attached is the job offers we discussed on the phone.</p>
                <br>
                Thank you,<br><br>
                Human Resources
            </body>
            </html>
        "@
        }
        Send-SHDMail @Message
        
        Sends an email using the required information with two attachments. 
    .EXAMPLE
        $Message = @{
            from = "HR <HumanResources@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "Test"
            BodyAsHtml = @"
            <html>
            <body>
                <center><h1>Sorry, Not Sorry</h1></center>
                <hr>
                <p>Sorry you didn't get the job. Maybe next time show up with clothing on.</p>
                <br>
                Thank you,<br><br>
                Human Resources
            </body>
            </html>
        "@
        }
        Send-SHDMail @Message

        This will send out an email without any attachments. 
    .EXAMPLE
        $Message = @{
            from = "HR <HumanResources@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "Test"
            Body = "Your Hired"
        }
        Send-SHDMail @Message

        Sends out a message using just a simple text in the body. 
    .EXAMPLE
        $Message = @{
            from = "HR <HumanResources@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "Test"
            Attachment = "C:\temp\JobOffer1.pdf","C:\temp\JobOffer2.pdf"
        }
        Send-SHDMail @Message

        This will send out an email that is blank with attached items. 
    .EXAMPLE
        $Message = @{
            from = "Notify <Notify@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "$SomethingWrong"
            PlainText = $true
        }
        Send-SHDMail @Message

        This will send out an unsecured email in plain text. 
    .EXAMPLE
        $Message = @{
            from = "Notifiy <Notify@example.com>"
            To = "IT@example.com"
            SMTPUsername = "SMTPUser"
            SMTPPassword = "SMTPPassword"
            SMTPServer = "mail.example.com"
            SMTPPort = "2525"
            Subject = "Server Down"
            CC = "ITManagers@Example.com"
            BCC = "CFO@Example.com"
            PlainText = $True
            Priority = "High" 
            BodyAsHTML = @"
            <html>
                <body>
                    <center><h1>SERVER DOWN!</h1></center>
                </body>
            </html>
        "@
        }
        Send-SHDMailMessage @Message
    .OUTPUTS
        no Output. 
    .NOTES
        Author: David Bolding
        Date: 09/8/2021
    .LINK
    #>
    [cmdletbinding()]
    param (
        [parameter(Mandatory = $true)][String]$From,
        [parameter(Mandatory = $true)][String]$To,
        [parameter(Mandatory = $true)][String]$SMTPUsername,
        [parameter(Mandatory = $true)][String]$SMTPPassword,
        [parameter(Mandatory = $true)][String]$SMTPServer,
        [parameter(Mandatory = $true)][String]$SMTPPort,
        [parameter(Mandatory = $true)][String]$Subject,
        [Switch]$PlainText,
        [string]$Body,
        [String]$BodyAsHTML,
        [String[]]$Attachment,
        [string]$CC,
        [string]$BCC,
        [Validateset("High","Low","Normal")][String]$Priority
    )
    # Server Info
    $SmtpServer = $SmtpServer
    $SmtpPort = $SmtpPort

    # Creates the message object
    $Message = New-Object System.Net.Mail.MailMessage $From, $To

    If ($PSBoundParameters.ContainsKey("CC")) {
        $Message.CC.Add($CC)
    }
    If ($PSBoundParameters.ContainsKey("BCC")) {
        $Message.Bcc.Add($BCC)
    }
    If ($PSBoundParameters.ContainsKey("Priority")) {
        $Message.Priority = $Priority
    } else {
        $Message.Priority = "Normal"
    }
    # Builds the message parts
    
    $Message.Subject = $Subject
    
    if ($PSBoundParameters.ContainsKey("Body")) {
        $Message.IsBodyHTML = $false
        $Message.Body = $Body
    }
    elseif ($PSBoundParameters.ContainsKey("BodyAsHTML")) {
        $Message.IsBodyHTML = $true
        $Message.Body = $BodyAsHTML
    }
    else {
        $Message.IsBodyHTML = $false
        $Message.Body = ""
    }
    
    if ($PSBoundParameters.ContainsKey('Attachment')) {
        foreach ($attach in $Attachment) {
            $message.Attachments.Add("$Attach")
        }
    }
    
    # Construct the SMTP client object, credentials, and send
    $Smtp = New-Object Net.Mail.SmtpClient($SmtpServer, $SmtpPort)
    if ($PlainText) { 
        $Smtp.EnableSsl = $true 
    }
    else { 
        $Smtp.EnableSsl = $true 
    }
    $Smtp.Credentials = New-Object System.Net.NetworkCredential($SMTPUsername, $SMTPPassword)
    $Smtp.Send($Message)

    #Closes the message object and the smtp object. 
    $message.Dispose()
    $Smtp.Dispose()
}    
    

Examples

        $Message = @{
            from = "HR <HumanResources@Example.com>"
            To = "Somebody@Example.com"
            SMTPUsername = "SMTP2GoUsername"
            SMTPPassword = "SMTP2GoPassword"
            SMTPServer = "mail.smtp2go.com"
            SMTPPort = "2525"
            Subject = "Job Offers"
            Attachment = "C:\temp\JobOffer1.pdf","C:\temp\JobOffer2.pdf"
            BodyAsHtml = @"
            <html>
            <body>
                <center><h1>Congradulation</h1></center>
                <hr>
                <p>Attached is the job offers we discussed on the phone.</p>
                <br>
                Thank you,<br><br>
                Human Resources
            </body>
            </html>
        "@
        }
        Send-SHDMail @Message

In this example, I am using the SMTP2go service to send a job offer letter to a new employee. It contains the attachment flag with two offers. Each attachment is separated with a comma as this is a list of strings. The Body is an HTML using the BodyAsHTML flag. The BodyAsHTML has some custom formatting to make it look somewhat nice.

$Message = @{
    from = "HR <HumanResources@Example.com>"
    To = "Somebody@Example.com"
    SMTPUsername = "SMTP2GoUsername"
    SMTPPassword = "SMTP2GoPassword"
    SMTPServer = "mail.smtp2go.com"
    SMTPPort = "2525"
    Subject = "Thank you"
    BodyAsHtml = @"
            <html>
            <body>
                <center><h1>Sorry, Not Sorry</h1></center>
                <hr>
                <p>Sorry you didn't get the job. Maybe next time show up with clothing on.</p>
                <br>
                Thank you,<br><br>
                Human Resources
            </body>
            </html>
        "@
}
Send-SHDMail @Message

In this example, we are once again using the SMTP2Go to send a rejection letter. No attachments are present on this email. The bodyashtml string is set with a nice custom HTML page.

$Message = @{
    from = "Notify <Notify@Example.com>"
    To = "Somebody@Example.com"
    SMTPUsername = "SMTP2GoUsername"
    SMTPPassword = "SMTP2GoPassword"
    SMTPServer = "mail.smtp2go.com"
    SMTPPort = "2525"
    Subject = "Server Down"
    Body = "XYZ Server Is Down"
}
Send-SHDMail @Message

In this example, we are sending out a notification email using a different user than before. We are using the same smtp2go, but you can use any server with the username and password you like. The body is a basic string with no HTML formating.

$Message = @{
    from = "Notify <Notify@Example.com>"
    To = "Somebody@Example.com"
    SMTPUsername = "SMTP2GoUsername"
    SMTPPassword = "SMTP2GoPassword"
    SMTPServer = "mail.smtp2go.com"
    SMTPPort = "2525"
    Subject = "$SomethingWrong"
    PlainText = $true
}
Send-SHDMail @Message

In this example, we are sending a notification email without a body. We are using a custom variable for the subject line. We are also sending this without the SSL encryption as some legacy systems don’t understand SSL encryption.

$Message = @{
    from = "Notifiy <Notify@example.com>"
    To = "IT@example.com"
    SMTPUsername = "SMTPUser"
    SMTPPassword = "SMTPPassword"
    SMTPServer = "mail.example.com"
    SMTPPort = "2525"
    Subject = "Server Down"
    CC = "ITManagers@Example.com"
    BCC = "CFO@Example.com"
    Priority = "High" 
    BodyAsHTML = @"
            <html>
                <body>
                    <center><h1>SERVER DOWN!</h1></center>
                </body>
            </html>
        "@
}
Send-SHDMailMessage @Message

In this example, we are Sending a Carbon copy to the IT manager and a blind carbon copy of the email to the CFO with a high priority.

Notes

  • The Body and BodyAsHTML can conflict with each other. If you do both the Body and BodyAsHTML, by default, the body will be selected. If you do not put in a body or bodyashtml, it will send the email with a body of “”.
  • This script requires you to have an SMTP service like SMTP2Go.

Conclusion

That my friends is how you Send Mail With Powershell.

WSD Printers IP Address

WSD Printers IP Address

WSD is an awesome service for printers. It goes out and finds a printer on the network and adds it accordingly. It does all the IP address stuff for you. Which is awesome. It even tells you that it was set up as a wsd by naming the port wsd and some code. Super friendly. However… What happens when DHCP changes that IP address because someone forgot to do a reservation? How do you get that IP address? Believe it or not, that IP address is stored in the registry for the most part. Its located under the Hardware Local Machine > System > Current Control Set > Enum > SWD > DAFWSDProvider. Each entry has a friendly name and location information. The location information has the IP address like a web page. Powershell can give you this information pretty quickly with a single line as well. Let’s take a look.

Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\DAFWSDProvider | foreach-object { $_ | Get-ItemProperty | Select-Object FriendlyName, LocationInformation }

Get-childitem is normally used in directories. The registry is a type of directory with files inside of it. So we use the get-childitem and treat the registry as a file path. We navigate to the DAFWSDProvider. Then we look at each file or in this case item property with Get-ItemProperty. We are looking for that FriendlyName and the LocationInformation. The location information will look like https://192.168.2.62/something because it is treating the IP address as a webpage. Still unsure why. The above command will list all of the printers like this. If it has an IP address, it will appear there.

Now we can wrap this up in a nice little function for remote computers.

The Script

function Get-SHDWDSPrinters {
    [cmdletbinding()]
    param (
        [string[]]$ComputerName = $Env:COMPUTERNAME
    )
    foreach ($Computer in $ComputerName) {
        Invoke-Command -ComputerName $Computer -ScriptBlock {
            Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\DAFWSDProvider | foreach-object { $_ | Get-ItemProperty | Select-Object FriendlyName, LocationInformation }
        }
    }
}
Oneliner Password Generator

Oneliner Password Generator

Passwords, Passwords, and more Passwords. Let’s generate a 16 character password that is complex and random that you will have to save into your password manager because you will never remember it.

The Single Line

[string]::Join("",(1..16 | ForEach-Object {[char](Get-Random -Minimum 32 -Maximum 126)}))        

The Breakdown

Lets break it down and then make a function for easier use. We are going to use the concept of PEMDAS. For this breakdown.

Get-Random -Minimum 32 -Maximum 126

This gives us a random number between 32 and 126. Why is this important. The next part is why. We are grabbing a character of X, [char](x), These are considered password-safe characters of the ASCII set.

 1..16 | foreach-object {SomeCode}

This part repeats everything in the “some code” area 16 times. So we are grabbing 16 chars. Each loop occurs separately. This creates 16 characters that take up a different line on the shell prompt each time it runs. That’s where the next part comes into play.

[string]::Join("", array )

This part of the script is a string function that joins each part of the array together. Notice the “” part. This adds items inside the array. So if you want the password to have 7 every join, then place “7” here.

Now when you combine all this together. We create an array of random password-safe characters and join them all together. With their powers combined, we have a potential password.

Lets make a Function

function Get-SHDPassword {
    [cmdletbinding()]
    Param (
        [int]$Length = 16,
        [int]$Count = 1
    )
    for ($I=0;$I -lt $Count;$I++) {
        [string]::Join("",(1..$Length | ForEach-Object {[char](Get-Random -Minimum 32 -Maximum 126)}))        
    }
}

Here we added a count, so we can make more than one and choose from it. By default, we are setting them to 16 and to 1. This way we have a 16 character password that is done only once.

That’s all folks, let me know if you have any questions or corrections.

Exchange Online – Mailbox Size Audit

Exchange Online – Mailbox Size Audit

Here is a little powerhouse script I wrote to audit the mailbox sizes. The focus recently is to see who’s mailbox sizes are about to be over and if it’s the deleted folder. I can’t show you all of the scripts, but I can show you part of it. This part will pull the data down in such a way that you can view the mailbox sizes, who has the largest mailboxes, what folder is the largest in those mailboxes, and what their deleted folder is sitting at. Let us take a look at the script itself.

The Script

function Get-SHDEXOMailboxSizeAudit {
    [cmdletbinding()]
    param (
        [pscredential]$Credential
    )
    Begin {
        
        #Installs required modules
        Write-Verbose "Installing required modules"
        if (!(Get-InstallEdModule ExchangeOnlineManagement)) { Install-Module ExchangeOnlineManagement }

        Write-Verbose "Checking and importing required modules"
        # Starts importanting required modules
        if (!(Get-Command Connect-ExchangeOnline)) { Import-Module ExchangeOnlineManagement }

    }
    process {
        
        #Connecting to exchange. Grabing credentials. 
        if (!($PSBoundParameters.ContainsKey('Credential'))) {
            $Credential = (Get-Credential) 
        }
        Connect-ExchangeOnline -credential $Credential
 
        #Grabs all the mailboxes at once. As this report will look at all the mailboxes. 
        $Mailboxes = Get-Mailbox

        #Starts looping through each mailbox
        For ($M = 0; $M -le $Mailboxes.count; $M++) {

            Write-Verbose "Gathering Info on: $($Mailboxes[$M].UserPrincipalName)"

            #Grabs the mailbox stats.
            $Stats = Get-EXOMailboxStatistics $mailboxes[$M].UserPrincipalName
            $FolderStats = Get-EXOMailboxFolderStatistics $mailboxes[$M].UserPrincipalName

            #Starts looping through those folders to get their sizes. 
            $MainFolderStats = foreach ($Folder in $FolderStats) {
                $FolderSize = [math]::Round((($Folder.FolderSize.split('(')[1].split(' ')[0] -replace ',', '') / 1gb), 2)
                [pscustomobject]@{
                    Name = $Folder.name
                    Size = $FolderSize
                }
            }

            #Adds this information to the mailbox object
            $Mailboxes[$M] | add-member -Name "Statistics" -MemberType NoteProperty -Value $Stats
            $Mailboxes[$M] | add-member -Name "FolderStats" -MemberType NoteProperty -Value $MainFolderStats
        }

        #Creates a return value. 
        $Return = foreach ($Mail in $Mailboxes) {

            #Starts looking at mailboxes that are not the discovery mailbox.
            if (!($mail.UserPrincipalName -like "DiscoverySearchMailbox*")) {

                #Grabs the deleted folder as that is a folder we want to see in this report. 
                $Deleted = $Mail.FolderStats | where-object { $_.name -like "Deleted Items" }

                #Grabs the largest folder. If  it's not the deleted folder, then we might want to increase their mailbox sizes.
                $LargestFolder = $Mail.FolderStats | Sort-Object Size -Descending | select-object -first 1 

                #Doing some math on a string. The string format (# bytes). Thus, we work the string to get the bytes. Divide and round. 
                $Size = [math]::Round(($Mail.Statistics.TotalItemSize.value.tostring().split('(')[1].split(' ')[0].replace(',', '') / 1gb), 2)

                #Grabs the mailboxes percentage.
                $DeletedToMailboxPercent = [math]::Round((($Deleted.Size / $size) * 100), 0)

                #Outputs the data to the return value. 
                [pscustomobject]@{
                    DisplayName             = $Mail.Displayname
                    UserPrincipalName       = $Mail.UserPrincipalName
                    MailboxSize             = $Size
                    LargetsFolder           = $LargestFolder.Name
                    LargetsFolderSize       = $LargestFolder.Size
                    DeletedItemSize         = $Deleted.Size
                    DeletedToMailboxPercent = $DeletedToMailboxPercent
                }
            }
        }
        
        #Disconnects exchange
        Disconnect-ExchangeOnline -confirm:$false > $null
    }
    End { 
        $Return | sort-object MailboxSize -Descending 
    }
}

The breakdown

First this script is designed to work on powershell 7. It will not work on powershell 5.

The first part we come to is the [pscredential] object. Notice it’s not mandatory. Notice no pipelining either. I have found PS credentials pipped intend to do very poorly. So, it’s simple, bam wam done.

Begin

Inside our begin tab, we have the module setup. We check installed modules for exchangeonlinemanagement. if it’s there, we ignore it and import the module, if it’s not we install it. Same way with importing. If it’s imported, then we do nothing, if it’s not, we import it.

Process

Next, we grab credentials if need be and connect to exchange. We use the get-credential command to grab the credentials. Then we use the connect-exchangeonline command to connect to the exchange online.

Once we are connected, the magic can start. This is the sweetness of this script. The first step is to grab all of the mailboxes at once with Get-Mailbox. Then we start a loop, not any loop, a for a loop. Wait! David, WHY A FOR LOOP! It’s simple, we want to add information to the index. So, we need to be able to call the index. We use the Get-EXOMailboxStatistics and choose the userprincipalname of the index we are looking for. We do the same thing with Get-EXOMailboxFolderStatistics. These gives us some clear stats we can use later on in the script. Now we loop through the folder stats that we just collected and math ourselves some bytes to gb. See the output of the get-exomailboxfolderstatistics looks like “24mb (#### bytes). So we need to filter that out and get the ####. I use a split to do this. I split out the string at the ‘(‘. This way, the bytes are on object 1. Then we split it again by the space. Now the bytes are on the 0 object. Then we replace the ‘,’ with nothing. Now we divide all that by 1gb to convert it to gbs. Then we drop that all into the mainfolderstats. Next, we add all of that information into the mailbox variable we created before this looping madness using add-member. We are adding it as a noteproperty.

Now we have prepped our data, it’s time to sort it. We start the loop once more, but this time it’s simple for each loop as we don’t need the array index value. We first grab all the deleted items folder. Then we grab the largest folder using the sort-object command on the size object of the folderstats note property that we made in the last step. Then we do some math. Like before to get the mailbox overall size. Finally, we grab the deleted mailbox percentage with a little more math. This time its percentage math. Now we have all of this useful information we use our pscustomobject and put it all together.

Then we disconnect using the disconnect-exchangeonline command.

End

Finally we display the return information inside our end tab. We sort the object by the mailbox size in a descending order.

SHD Resource – User to Groups

SHD Resource – User to Groups

This little guy is a simple dynamic parameter resource for you all. Take a look at my previous blog post about how these parameters work. Totally worth adding these things to your scripts. This script is simple, it uses the add-adgroupmemeber and dynamic parameters to help pad against mistakes.

The Script

function Set-SHDADGroupMemebers {
    [cmdletbinding()]
    param (
        [parameter(mandatory=$true)][validateset('Add','Remove')][string]$Action,
        [securestring]$Credential,
        [switch]$Output
    )
    DynamicParam {
        
        
        # Set the dynamic parameters' name
        $ParamName_portgroup = 'Group'
        # Create the collection of attributes
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $true
        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute) 
        # Create the dictionary 
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        # Generate and set the ValidateSet 
        $arrSet = (Get-ADGroup -Filter *).name
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)    
        # Add the ValidateSet to the attributes collection
        $AttributeCollection.Add($ValidateSetAttribute)
        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_portgroup, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParamName_portgroup, $RuntimeParameter)

        
        # Set the dynamic parameters' name
        $ParamName_datastore = 'Username'
        # Create the collection of attributes
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        # Create and set the parameters' attributes
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $true
        # Add the attributes to the attributes collection
        $AttributeCollection.Add($ParameterAttribute)  
        # Generate and set the ValidateSet 
        $arrSet = (Get-ADUser -Filter *).name
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
        # Add the ValidateSet to the attributes collection
        $AttributeCollection.Add($ValidateSetAttribute)
        # Create and return the dynamic parameter
        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParamName_datastore, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParamName_datastore, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    begin{
        $Group = $PsBoundParameters[$ParamName_portgroup]
        $username = $PsBoundParameters[$ParamName_datastore] 
    }
    process {
        if ($PSBoundParameters.ContainsKey('Credential')) {
            if ($Action -like "Add") {
                Add-ADGroupMember -Identity $group -Members $username -Credential $Credential
            } elseif ($Action -like "Remove") {
                Remove-ADGroupMember -Identity $group -Members $username -Credential $Credential
            } else {
                Get-ADGroupMember -Identity $group -Credential $Credential
            }
        } else {
            if ($Action -like "Add") {
                Add-ADGroupMember -Identity $group -Members $username
            } elseif ($Action -like "Remove") {
                Remove-ADGroupMember -Identity $group -Members $username
            } else {
                Get-ADGroupMember -Identity $group
            }
        }
    }
    end {
        if ($Output) {
            if ($PSBoundParameters.ContainsKey('Credential')) {
                Get-ADGroupMember -Identity $Group -Credential $Credential
            } else {
                Get-ADGroupMember -Identity $group
            }
        }
    }
}

Example 1

Set-SHDADGroupMemebers -Action Add -Group Administrators -Username Adam.Long -Output

Adds Adam.Long to the administrators group. Then it outputs all the users inside that group.

Example 2

Set-SHDADGroupMemebers -Action Remove -Group Administrators -Username Adam.Long

Removes Adam.Long from the Administrators group without outputting any additional information.