Sunday, December 20, 2015

Get-SCSMCategories

Hello everyone!

I have been doing quite a bit of DevOps work lately and I received a request the other day that might be worth sharing.

Our SCSM Administrator needed to get an export of our current categories, and he had found a script online to do so; however, it was only hard coded to go 3 levels deep and our categories would go much deeper than that. . He asked me if I could take a look into why this was happening, and it turned out that the way that the script works is that it was only hard coded to go 3 levels deep, so I wrote a function to recursively go through the main class, and its children and print out the categories regardless of the levels that you might have.

Import-Module SMLets

function get-childLists($listObjects)
{
    $count ++
    foreach($obj in $listObjects)
    {
        $id = $obj.id
        "$("`t"*$count) - $($obj.DisplayName)" | Out-File 'C:\FILE\PATH.txt' -Append
        $children = Get-SCSMEnumeration -ComputerName $scsm | ? Parent -match $id
        if($children -ne $null)
        {
            get-childLists $children, $listName
        }
    }

}

$scsm = 'YOURSCSMSERVER'
$areas = @("ActivityAreaEnum",
"IncidentClassificationEnum",
"ServiceRequestAreaEnum")

foreach($list in $areas)
{
    $count = -1
    $enumLists = Get-SCSMEnumeration -ComputerName $scsm | ? Name -eq $list
    get-childLists $enumLists
}

Let me know what you think, and if you all find this useful then I can probably make it into an actual advance function.

Kind regards,
Me

Friday, October 23, 2015

PowerShell 5.0

Hello everyone! 

I might be a little late with this but the other day I found out a nice little feature of the new PowerShell 5.0, and that is that not only does it come with nice color coding but it also supports Control+C, Control-X and Control-V ! A feature that was long overdue. 

Finding this new feature out made me realize that I haven't even looked into the new features, and so I did. Here are some of the features that I'm really excited about:


  1. Microsoft.PowerShell.Archive: Allows us to manage, extract and even create compressed files (zip files)
  2. PowerShell Gallery: A central repository for all PowerShell things. If it is deployed correctly and it actually is used by the community it will be a great resources, specially with the new 'PowerShellGet' Module that allows you to get stuff from the Gallery.
  3. The new -Depth parameter for the Get-ChildItems cmdlet sounds great, so far I had to use a function that I wrote to get this accomplished. It'll be nice to have it already built in.
  4. Transcript is now supported in the ISE and a new GPO can be enabled to setup global transcripts.
  5. Get-Clipboard and Set-Clipboard cmdlet sound like might come in handy too! They support images, audio files, file lists, and text.
Overall it seems that this new version has come with quite a few changes  very interesting changes. It's all very exciting. For more details be sure to visit the technet article.


Let me know what you guys think about version 5.0, and if you are exited about any particular feature, and why!

Kind regards,
Me.

Edit: It has been pointed out to me that it is not PowerShell that is now able to handle the copy, cut and paste features but the shell. Thanks to https://www.reddit.com/user/No1Asked4MyOpinion for pointing it out. 

Wednesday, August 12, 2015

Installing .NET Framework 3.5 offline

Hello everyone!

I just wanted to put this out there because I didn't think it was very clear. If you want to install Windows .NET Framwork 3.5 without access to the Internet you'll have to perform the installation from your disk:





















By selecting the 'Specify an alternaet source path' option. If you try to continue without doing this the installation will fail stating that it was not able to connect to the Windows Update service. If you try to download the installer from the website, filename "dotnetfx35", it will fail with the same error. ( I did read something about being able to perform an extraction on the dotnetfx35 file with a /x switch on installation but I wasn't able to get it working, if you know how this would be done drop me a line I'd love to find out)

The reason for this is that by default the installation will attempt to get the most recent updates for the installation.

Well I hope this might help someone out there.

Kind regards,
Me.

------------------------------

Oh yeah, you are also able to get this done by running the following command:

DISM /Image:C:\test\offline /Enable-Feature /FeatureName:NetFx3 /All /LimitAccess /Source:D:\sources\sxs

Where /Source: is the location in your DVD. 

Monday, May 18, 2015

The Case of the Mysterious Blank Desktop, part 2?

Hello everyone, 

I recently experienced an issues login into a Windows Server 2008 R2. Whenever I would log in, whether through RDP or VM Console, the only thing that would load was a blank desktop. No errors, no windows, no explorer. I wasn't able to use Remote Tools, but I was able to use PSRemote and from there was was able to pull the following log:

[HOSTNAME]: PS C:\Users\USERNAME\Documents> Get-EventLog -LogName Application | ?{$_.Source -eq 'Winlogon'} | ?{$_.EntryType -eq 'Warning'} | select -First 1 | fl * -Force


EventID            : 4006
MachineName        : HOSTNAME.DOMAIN.com
Data               : {5, 0, 0, 0}
Index              : 40795
Category           : (0)
CategoryNumber     : 0
EntryType          : Warning
Message            : The Windows logon process has failed to spawn a user application. Application name: . Command line
                      parameters: C:\Windows\system32\userinit.exe.
Source             : Winlogon
ReplacementStrings : {, C:\Windows\system32\userinit.exe}
InstanceId         : 2147487654
TimeGenerated      : 5/18/2015 8:47:26 PM
TimeWritten        : 5/18/2015 8:47:26 PM
UserName           :
Site               :
Container          :

I quickly pulled out Google and found the following article, but to my demise after checking the local group I found that the users were already added.

[HOSTNAME]: PS C:\Users\USERNAME\Documents> net localgroup users
Alias name     users
Comment        Users are prevented from making accidental or intentional system-wide changes and can run most applications

Members

-------------------------------------------------------------------------------
NT AUTHORITY\Authenticated Users
NT AUTHORITY\INTERACTIVE
DOMAIN\Domain Admins
DOMAIN\Domain Users
The command completed successfully.

Great so now what? I searched the internet trying to find someone who might have experienced a similar issue but i only found similar articles to the one linked above. I attempted to reboot, to remove the users Authenticated Users/Interactive off the group, reboot, add them again and reboot once again, but nothing.

I did have some idea of what might be causing the issue based on the article though, so I attempted moving the computer to a different OU that doesn't require UAC to be turned on. I updated the group policy and rebooted but still nothing. Frustrated I went to to Regedit and attempted to load the UAC settings to find out if it was turned on or off by using the following command that I got from this article.

[HOSTNAME]: PS C:\Users\USERNAME\Documents> (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).EnableLUA

To my surprise I got nothing. Null return value. I thought that was weird, so I confirmed on a different server with the same version to ensure that the command was correct, and it was returning a result of 0. So I re-added the setting using the following command, also found in the article. 

[HOSTNAME]: PS C:\Users\USERNAME\Documents> Set-ItemProperty -Path registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\policies\system -Name EnableLUA -Value 0

I rebooted and the server logged back in without any issues. I have no idea how this registry could have been deleted, or why it had this effect so if anyone has any more information please be sure to share.

I hope this might help someone out there. Do let me know if you have any questions, issues or comments. 

Kind regards, 
Me.

Thursday, May 14, 2015

Windows 10 Preview

Hello everyone, 

For the first time in my life I have a spare computer that I can use to test things on. So I signed up for Windows Insider and installed Windows 10 on my laptop which I have been using on the regular bases. I'm not much of a writer so I won't even attempt to do a full review on my experience, but I did want to comment on the quality of people that I have found on the Microsoft reviewing forums. 

The truth is that people are nasty. I went through quite a few posts and most of the posts was of people complaining and calling that Windows will be a failure. All because of the way that the UI looks. Instead of providing constructive criticism to help develop a better operating system we have people crying about the way that the task bar is, or the way the start menu looks, or the how the icons are flat! I mean come on people! We are all tech savvy here what does it matter what the system looks like as long as it works? 

Windows is quite frankly implementing some really good features not only on their desktop environment but on the server environment, and so what if it looks different? We shouldn't be using the system for how it looks but for what we can do with it. After all we can always hack our way to change the way things look. 

I do hope that people can change their outlook on how things are going with Windows 10. It will either be a success or a failure, and if its a failure its not like it was a few years back and we actually have options.

Kind regards, 
Me.

Get-DFSNFolderTarget

Hello! 

I been wondering how I can find out where a DFS target is through PowerShell for quite some time now, and perhaps I didn't look hard enough because today I found the cmdlet in the subject. 

Get-DfsnFolderTarget [-Path] <String> [[-TargetPath] <String> ] [-CimSession <CimSession[]> ] [-ThrottleLimit <Int32> ] [ <CommonParameters>]

It is a very handy command that will return the following output: 

PS Microsoft.PowerShell.Core\FileSystem::\\DFS\SHARE\PATH> Get-DfsnFolderTarget \\DFS\SHARE\PATH | fl *


Path                  : \\DFS\SHARE\PATH
State                 : Offline
ReferralPriorityClass : sitecost-normal
NamespacePath         : \\DFS\SHARE\PATH
ReferralPriorityRank  : 0
TargetPath            : \\targetServer\Share
PSComputerName        :
CimClass              : Root/Microsoft/Windows/dfsn:MSFT_DfsNamespaceFolderTarget
CimInstanceProperties : {NamespacePath, ReferralPriorityClass, ReferralPriorityRank, State...}
CimSystemProperties   : Microsoft.Management.Infrastructure.CimSystemProperties

However, I have found it to be less than helpful if I don't know exactly 
where the DFS namespace ends and the targetPath begins, for example:

\\DFS\SHARE\PATH\Something\Folder 

If I try to find the root using this path I receive the following error:

Get-DfsnFolderTarget : Cannot get DFS folder properites on
"Microsoft.PowerShell.Core\FileSystem::\\DFS\SHARE\PATH\Something\Folder"
At line:1 char:1
+ Get-DfsnFolderTarget $((Get-Item .\).PSParentPath)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (MSFT_DfsNamespaceFolderTarget:ROOT\Microsoft\...aceFolderTarget) [Get-Dfs
   nFolderTarget], CimException
    + FullyQualifiedErrorId : Windows System Error 1722,Get-DfsnFolderTarget

Get-DfsnFolderTarget : A general error occurred that is not covered by a more specific error code.
At line:1 char:1
+ Get-DfsnFolderTarget $((Get-Item .\).PSParentPath)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (MSFT_DfsNamespaceFolderTarget:ROOT\Microsoft\...aceFolderTarget) [Get-Dfs
   nFolderTarget], CimException
    + FullyQualifiedErrorId : MI RESULT 1722,Get-DfsnFolderTarget

I am not sure why though. It is simple enough to get the information form the
GUI so perhaps is my inexperience or lack of knowledge, but why can't it be 
this easy on the cmd line as well?

I do hope that it is just my lack of knowledge and that maybe one of you
might show me the right way to get the DFSFolderTarget path. 

Kind regards, 
Me

Tuesday, April 28, 2015

Script Sharing - Advance functions to make managing file security easier

Hey everyone! 

I recently started trying to get a little bit more 'official' and started learning how to write Advance Functions and I wanted to share 4 functions that I believe are quite useful, and hope that someone might find helpful. 

Function #1:
function Enable-AclInheritanceOnFolder
{
    <#
      .SYNOPSIS
      This function will turn on inheritance on the folder
      .DESCRIPTION
      This function will turn inheritance on the folder provided, currenth path if no path is provided.
      .EXAMPLE
      Enable-AclInheritanceOnFolder -path 'C:\TestPath'
      .PARAMETER Path
      The path of the folder where the ACL should be added.
    #>
    [CmdletBinding()]
[OutputType([int])]
Param
(
        #Path
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the path where inheritance should be enabled?')]
        [Alias('Location')]
        [ValidateLength(3,250)]
        [string]$path
)
    begin
    {
        if($path.Length -eq 0)
        {
            Write-Verbose "No Path provided, assigning current location to path."
            $currentPath = (Get-Item .\).FullName
        }
        else
        {
            $currentPath = $path
        }
    }
    Process
    {
        try
        {
            Write-Verbose "Getting folder IO object for $currentPath"
            $folderInfo = New-Object IO.DirectoryInfo($currentPath)
            Write-Verbose "Getting the Folder Access Control List."
            $folderACLs = $folderInfo.GetAccessControl()    
            Write-Verbose "Enabling Inheritance in ACL Object."
            $folderACLs.SetAccessRuleProtection($false,$false)
            Write-Verbose "Attempting to save the changes."
            $folderInfo.SetAccessControl($folderACLs)
        }
        catch
        {
            Write-Verbose "There was an error adding the permissions."
            Write-Error $Error[0]
        }

        
    }
}

Function #2 
function Disable-AclInheritanceOnFolder
{
    <#
      .SYNOPSIS
      This function will turn off inheritance on the folder
      .DESCRIPTION
      This function will turn off inheritance on the folder provided, currenth path if no path is provided. 
      It will also require that you specify if the current rules should be kept and converted or if they 
      should be removed.
      .EXAMPLE
      Disable-AclInheritanceOnFolder -path 'C:\TestPath' -ConvertRules $true|$false
      .EXAMPLE
      Disable-AclInheritanceOnFolder -ConvertRules $true|$false
      .PARAMETER Path
      The path of the folder where the ACL should be added.
      .PARAMETER ConvertRules
      Bool to know if the current rules should be converted and kept or removed from the folder.
      If you remove the rules you might be loose access to the folder, be sure to take ownership of the
      folder first.
    #>
    [CmdletBinding()]
[OutputType([int])]
Param
(
        #Path
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the path where inheritance should be disabled?')]
        [Alias('Location')]
        [ValidateLength(3,250)]
        [string]$path,
        #PreserveInheritance
        [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='Should the current rules be removed or converted?')]
        [Alias('PreserveInheritance')]
        [bool]$RemoveCurrentRules
)
    begin
    {
        if($path.Length -eq 0)
        {
            Write-Verbose "No Path provided, assigning current location to path."
            $currentPath = (Get-Item .\).FullName
        }
        else
        {
            $currentPath = $path
        }
    }
    Process
    {
        try
        {
            Write-Verbose "Getting folder IO object for $currentPath"
            $folderInfo = New-Object IO.DirectoryInfo($currentPath)
            Write-Verbose "Getting the Folder Access Control List."
            $folderACLs = $folderInfo.GetAccessControl()    
            Write-Verbose "Enabling Inheritance in ACL Object."
            $folderACLs.SetAccessRuleProtection($true,$true)
            Write-Verbose "Attempting to save the changes."
            $folderInfo.SetAccessControl($folderACLs)
        }
        catch
        {
            Write-Verbose "There was an error adding the permissions."
            Write-Error $Error[0]
        }

        
    }
}

Function #3
function Add-AclToFolder
{
    <#
      .SYNOPSIS
      This function will add an Group to a folder's ACL.
      .DESCRIPTION
      This function will add the given SecurityGroup or user name to the specified folder.
      .EXAMPLE
      Add-ACL -Path 'C:\TestPath' -securityGroup 'DL-SECURITYGROUP-NAME'
      .PARAMETER Path
      The path of the folder where the ACL should be added.
      .PARAMETER securityGroup
      The security group name
      .PARAMETER AccessLevel
      The access level that you wish to give to the group. Choose between ReadAndExecute, Modify, FullControl
    #>
    [CmdletBinding()]
[OutputType([int])]
Param
(
        #Path
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the path where this group should be added?')]
        [Alias('Location')]
        [ValidateLength(3,250)]
        [string]$path,
#securityGroup
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage='What is the name of the group?')]
[Alias('group')]
[ValidateLength(3,50)]
[string]$securityGroup,  
        #AccessLevel
        [Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage='Access Level: ReadAndExecute, Modify, FullControl')]
[Alias('acl')]
[ValidateLength(3,15)]
[string]$accessLevel
)
    begin
    {
        Write-Verbose "Starting Add-AclToFolder Function."   
        if($path.Length -eq 0)
        {
            Write-Verbose "No path was provided. We are getting the current path."
            $currentPath = (Get-Item .\).FullName
        }
        else
        {
            $currentPath = $path
        }
    }
    Process
    {

        #region Add-ACL
        Write-Verbose "Process has started."
        try
        {
            Write-Verbose "Getting folder IO object for $currentPath"
            $folderInfo = New-Object IO.DirectoryInfo($currentPath)
            Write-Verbose "Getting the Folder Access Control List."
            $folderACLs = $folderInfo.GetAccessControl()    
            Write-Verbose "Creating new Security.AccessControl.FileSystemAccessRule."
            Write-Verbose "Security Group: $securityGroup"
            Write-Verbose "Access Level: $accessLevel"
            $newACL = New-Object System.Security.AccessControl.FileSystemAccessRule("$($securityGroup)",$accessLevel,"ContainerInherit, ObjectInherit", "None", "Allow")
            Write-Verbose "Adding the new Rule to the ACL object."
            $folderACLs.AddAccessRule($newACL)
            Write-Verbose "Attempting to save the changes."
            $folderInfo.SetAccessControl($folderACLs)
        }
        catch
        {
            Write-Verbose "There was an error adding the permissions."
            Write-Error $Error[0]
        }
    }
}

Function #4 
function Remove-AclFromFolder
{
    <#
      .SYNOPSIS
      This function will remove an Group or User from a folder's ACL.
      .DESCRIPTION
      This function will remove an Group or User from a folder's ACL. If no path is provided the script will use the current path.
      .EXAMPLE
      Remove-AclFromFolder -Path 'C:\TestPath' -securityGroup 'DL-SECURITYGROUP-NAME'
      .EXAMPLE
      Remove-AclFromFolder -securityGroup 'DL-SECURITYGROUP-NAME'
      .PARAMETER Path
      The path of the folder where the ACL should be removed.
    #>
    [CmdletBinding()]
[OutputType([int])]
Param
(
        #Path
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the path where th group should be removed from?')]
        [Alias('Location')]
        [ValidateLength(3,250)]
        [string]$path,
#securityGroup
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage='What is the name of the group?')]
[Alias('group')]
[ValidateLength(3,50)]
[string]$securityGroup
)
    begin
    {
        Write-Verbose "Starting Add-AclToFolder Function."   
        if($path.Length -eq 0)
        {
            Write-Verbose "No path was provided. We are getting the current path."
            $currentPath = (Get-Item .\).FullName
        }
        else
        {
            $currentPath = $path
        }
    }
    Process
    {

        #region Add-ACL
        Write-Verbose "Process has started."
        try
        {
            Write-Verbose "Getting folder IO object for $currentPath"
            $folderInfo = New-Object IO.DirectoryInfo($currentPath)
            Write-Verbose "Getting the Folder Access Control List."
            $folderACLs = $folderInfo.GetAccessControl()    
            Write-Verbose "Finding the rule to remove."
            $aclToRemove = $folderACLs.Access | ?{$_.IdentityReference -match $securityGroup}
            Write-Verbose "Removing $($aclToRemove.identityreference) from the ACL Object"
            $folderACLs.RemoveAccessRule($aclToRemove)
            Write-Verbose "Attempting to save the changes."
            $folderInfo.SetAccessControl($folderACLs)
        }
        catch
        {
            Write-Verbose "There was an error modifying the permissions."
            Write-Error $Error[0]
        }
    }
}

I believe that I been able to properly document the functions so there shouldn't be any confusion, but do let me know if you find any issues, comments or complaints. 

Kind regards, 
Me.

Monday, March 16, 2015

PowerShell ISE Cut (Ctrl + X)

Hello reader, 

I know its been a while since I posted anything but things have been a little dull and haven't had anything worth posting. Today was a bit different though, I encountered a quirky bug, and thought I could share it.  

It all happened this morning while I was working on a script and I tried to cut a line of the script and noticed that cut was not working. I thought that was weird, so I restarted PowerShell ISE hoping it just crapped out but without any success. I tried right clicking and selecting the Cut option but this also failed. I pulled up my handy dandy Google and found the following Microsoft Connect bug report: 

I saw one of the comments by 'Scott - Oregon' in which he stated that VNC Viewer was causing the issue and closing it would resolve the problem. I don't use VNC Viewer, but I did remember earlier while working with mRemoteNG that I accidentally clicked to remote into a server with VNC instead of RDP, so I closed mRemoteNG and voila it started working. 

I made sure to sign up and drop a comment on the Microsoft Connect post as it is still an active post. 

I hope this might help someone out there!

Kind regards,
Me


Tuesday, January 6, 2015

Exchange cmdlet: Get-User

Hello!

Its been a bit since I posted last mainly because I was out on vacations. I been back for a few days now but it had been a bit uneventful. Until today when I was tasked with creating a termination script for over 300 users. I was provided with a list and started working on writing a script that followed our procedures. I don't think I can go into details for privacy reasons but we make some changes to the settings on our Exchange side when we terminate the user, and i noticed that some of the users that I was provided were not Mailbox users but MailUsers and they even threw a few MailContacts in there too.

The command Get-Mailbox does not work for MailUsers or MailContact and they each have their own. I did some digging around to try to find the best way to identify the type of mailbox they are before trying to use the 'Get-' or the 'Set-' commands but I wasn't able to find much. I even reviewed all of the properties for Get-ADUser and Get-ADObject hoping to find anything that would identify all three of them without having to write extra code - I don't normally like to try to write extra code unless I believe its completely necessary. Unfortunately none of it brought me the answer I was seeking.

It wasn't until I was going through the cmdlets that are added with the Exchange Add-in that I found the cmdlet 'Get-User'. I been writing PowerShell for over 2 years now, and quite franky I've never had never seen this particular cmdlet, but it really did made everything so much easier.

Syntax:

Get-User [-Identity <UserIdParameter>] <COMMON PARAMETERS>
Get-User [-Anr <String>] <COMMON PARAMETERS>
COMMON PARAMETERS: [-AccountPartition <AccountPartitionIdParameter>] 
                   [-Arbitration <SwitchParameter>] 
                   [-AuditLog <SwitchParameter>] 
                   [-ConsumerNetID <NetID>] 
                   [-Credential <PSCredential>] 
                   [-DomainController <Fqdn>] 
                   [-Filter <String>] 
                   [-IgnoreDefaultScope <SwitchParameter>] 
                   [-Organization <OrganizationIdParameter>] 
                   [-OrganizationalUnit <OrganizationalUnitIdParameter>] 
                   [-PublicFolder <SwitchParameter>] 
                   [-ReadFromDomainController <SwitchParameter>] 
                   [-RecipientTypeDetails <RecipientTypeDetails[]>] 
                   [-ResultSize <Unlimited>] 
                   [-SoftDeletedUser <SwitchParameter>] 
                   [-SortBy <String>]

Source

The regular output is:

PS C:\> get-user Jonatan.Bernal

Name                                                            RecipientType
----                                                                 -------------
Jonatan Bernal                                              UserMailbox

It can if you format-list you get the full object: Deserialized.Microsoft.Exchange.Data.Directory.Management.User

The information provided is quite thorough and I don't have to bother what type of  mailbox it is which quite frankly I find very useful to only use 1 cmdlet and use the other ones only when they are needed.

I hope that this might help someone out there.

Feel free to leave me a comment and let me know if this was helpful or know any other interesting cmdlets!

Kind regards,
Me

Update: well it was pointed out to me by +Lincoln Reedy that the cmdlet that I shared about would unfortunately not work for MailContacts, which again can be a problem. He also shared with me the cmdlet 'Get-Recepient' which would work for any type of object. I haven't quite worked much with this new cmdlet yet, so I'm still doing some research because I saw that you can use an authentication type of federation, so I'm wondering if you can actually get recipients outside of your organization. I'll update when I have more information on this.