Loading...
Jason Yoder, MCT by Jason Yoder, Mct - 3M ago
For those of you who have taken any of my PowerShell classes, you know that I like to teach in code and I give it to you.  I also like to keep things organized and easy for use to move from one section to another.  I absolutly hate having to open multiple files to demonstrate a single topic so I need a way to present code in a clear and organized way.  Regions are great ways to do this.  A region is simply a block of collapsible code.

Here is a basic implementation:

#region

For ($X= 0; $X -lt 5; $X++) {
    Write-Host "I love regions!!!!"
}

#endregion 


You start with the commented line.  You cannot have a space between the ‘#’ and the word ’region’.  Also, you must use lower case characters.  To close the region, you use ‘endregion’.

You can add labels to these to help make your code more descriptive.

#region - Lesson 1

For ($X= 0; $X -lt 5; $X++) {
    Write-Host "I love regions!!!!"
}

#endregion - Lesson 1


Let’s take a look at this sample code:

#region - Lesson 1

For ($X= 0; $X -lt 5; $X++) {
    Write-Host "I love regions!!!!"
}

#endregion - Lesson 1

#region - Lesson 2

For ($X= 0; $X -lt 5; $X++) {
    Write-Host "I love regions!!!!"
}

#endregion - Lesson 2


My code in each chapter is usually a few hundred lines long so I open the chapter by pressing Ctrl-Mto collapse all.

#region – Lesson 1 …

#region – Lesson 2 …

The problem is when I expand #region – Lesson 1 to start the lesson using the ‘+’ symbol to the left of the region in the PowerShell ISE.

#region - Lesson 1

For ($X= 0; $X -lt 5; $X++) {…}
}

#endregion - Lesson 1


Notice that the FOR loops scripblock is still collapsed.  This makes this method of using regions a bit more difficult.  Here is the simple work around.  Select the line of the region and the expand it.

#region – Lesson 1 …

#region – Lesson 2 …


#region - Lesson 1

For ($X = 0; $X -lt 5; $X++) {…}
}

#endregion - Lesson 1

#region – Lesson 2 …

Now only that region is expanded along with any expandable code within that region.  A simple, yet very useful workaround.


 If I'm writing an article like this, you know that I am making a lot of updates to my PowerShell classes ;)

Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
I have always said that one of the greatest benefit to teaching PowerShell (and Windows) is that different people bring different ideas to the table.  Things get fun for me when someone looks at what we are doing from a different angle and asked an interesting question.

This week’s class in Fort Wayne produced one of those questions.  We were looking at some of the options that are available to use with creating variables with the New-Variable cmdlet.  In particular, we were looking at constants.  Let’s build one.

PS C:\> New-Variable -Name Test1 -Value ([Bool]$True) -Option Constant

Now let’s take a look at the variable object.
Name        : Test1
Description :
Value       : True
Visibility  : Public
Module      :
ModuleName  :
Options     : Constant
Attributes  : {}


We can see from the Optionsproperty that we have created a constant.  We are going to attempt to change that value of this constant to FALSE.

PS C:\> Set-Variable -Name Test1 -Value $false
Set-Variable : Cannot overwrite variable Test1 because it is read-only or constant.
At line:1 char:1
+ Set-Variable -Name Test1 -Value $false
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (Test1:String) [Set-Variable], SessionStateU
   nauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable,Microsoft.PowerShell.Commands.SetVar
   iableCommand

This is what we expected.  By definition, a constant cannot be changed.  We also attempted to change it with the –Force parameter.

PS C:\> Set-Variable -Name Test1 -Value $false -Force
Set-Variable : Cannot overwrite variable Test1 because it is read-only or constant.
At line:1 char:1
+ Set-Variable -Name Test1 -Value $false -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (Test1:String) [Set-Variable], SessionStateU
   nauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable,Microsoft.PowerShell.Commands.SetVar
   iableCommand

Again, the expected results.  Well, this is where that question comes into play.  What if you re-cast the variable?  OK, let’s give this a try.

PS C:\> [Bool]$Test1 = $False

PS C:\> $Test1
False

Wait… What?  You cannot even get rid of a constant with Remove-Variable but here we changed it.  OK, did we really change the value or did it delete the variable and then recreate it?  Here is another test.

PS C:\> Set-Variable -Name Test3 -Value ([Bool]$True) -Option Constant -Description "This is a test"


Here we included a description which you can see in the variable objects properties.

Name        : Test3
Description : This is a test
Value       : True
Visibility  : Public
Module      :
ModuleName  :
Options     : Constant
Attributes  : {}

We are going to change this variable using the same successful method from above.

PS C:\> [Bool]$Test3 = $False

PS C:\> $Test3
False


And now let’s look at the properties to see if the description is still there.
PS C:\> Get-Variable -Name Test3 | Select-Object -Property *


PSPath        : Microsoft.PowerShell.Core\Variable::Test3
PSDrive       : Variable
PSProvider    : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name          : Test3
Description   : This is a test
Value         : False
Visibility    : Public
Module        :
ModuleName    :
Options       : Constant
Attributes    : {System.Management.Automation.ArgumentTypeConverterAttribute}



The description is still there.  So, I guess there is a way to change the value of a constant without restarting PowerShell
Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Jason Yoder, MCT by Jason Yoder, Mct - 4M ago
Often while teaching PowerShell, we get into a discussion about how someone, usually me, types this:

Get-Help Get-Date

Instead of

Get-Help –Name Get-Date

PowerShell parameters utilize positioning.  Good authors of cmdlets will determine which parameter will be the most frequently used and put that parameter in the first position.  That means if the user types a cmdlet, they can immediately provide the data for that parameter without calling the parameter name.  Take a look at the –Name parameter of Get-Help
-Name
    Gets help about the specified command or concept. Enter the name of a cmdlet, function, provider,
    script, or workflow, such as `Get-Member`, a conceptual topic name, such as `about_Objects`, or an
    alias, such as `ls`. Wildcard characters are permitted in cmdlet and provider names, but you
    cannot use wildcard characters to find the names of function help and script help topics.
   
    To get help for a script that is not located in a path that is listed in the Path environment
    variable, type the path and file name of the script.
   
    If you enter the exact name of a help topic, Get-Help displays the topic contents. If you enter a
    word or word pattern that appears in several help topic titles, Get-Help displays a list of the
    matching titles. If you enter a word that does not match any help topic titles, Get-Help displays
    a list of topics that include that word in their contents.
   
    The names of conceptual topics, such as `about_Objects`, must be entered in English, even in
    non-English versions of Windows PowerShell.
   
    Required?                    false
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false

Two things to take note of.  First of all, the type of data that this parameter accepts is [String].  The second is the value of Position which is zero.  That means if the user types the cmdlet Get-Help and then a value of the type string, that value will be the argument for the –Name parameter. 

I often stress the need for full command syntax in scripts so everyone knows what parameters you are using but I am also guilty of using positional parameters for my more common cmdlets like Get-Help and Where-Object.  Here is some code to help you see the parameter in the first position and what type of data it expects.  Just be forwarded, it will load all of your modules into memory.
$Commands = Get-Command

ForEach ($CMDin $Commands) {
    $Obj = [PSCustomObject]@{
        'Cmdlet'= $CMD.Name
        'PositionOne'= (($CMD| Get-Help).parameters.parameter | where position -eq 0).Name
        'Type'= (($CMD| Get-Help).parameters.parameter| where position -eq 0).Type.Name
        }

    Write-OutPut$Obj
}


Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
How to start a PowerShell Script from a Batch File

In last week’s PowerShell class in Phoenix, we had a last minute question.  It involved trying to simplify the launching of a PowerShell script for users.  Having end users working with PowerShell has long been a cumbersome task.  End users like a GUI.  We can put a GUI interface on top of our code, but it is difficult to do manually or you need a third party solution.  When you build a GUI, it also takes an additional skill set that most IT Pros do not have.

We decided to go with a batch file.  Yes, I know.  Old tech but we will give it new life.  Here is our test code for this project. We saved this file as c:\ps\Test1.ps1.

Write-Host "I work!!!" -BackgroundColor DarkMagenta

Yes, I know.  Not exactly exciting.  The purpose of this is to get it to launch with a batch file.

We looked at the PowerShell.exe Command-Line Help (https://docs.microsoft.com/en-us/powershell/scripting/core-powershell/console/powershell.exe-command-line-help?view=powershell-5.1) to see how to launch PowerShell with a script from the command line at the same time.  We came up with:

PowerShell.exe –File C:\PS\Test1.ps1

We saved this command line into a batch file in the same directory as the script and was able to launch it from a desktop shortcut icon.  Right now, this is a viable option.

What about using parameters?  This is a bit more difficult.  The original objective was to do it from a DOS command prompt, but when we add parameters, the process is just as complex as doing it PowerShell if not more.  Here is our new code.

Param ($ComputerName)
Write-Host "I work!!!" -BackgroundColor DarkMagenta
Write-Host $ComputerName

Again, I know.  Real advanced.  This is what our batch file looks like now:

PowerShell.exe –File C:\PS\Test1.ps1 –ComputerName INDY-DC1
The original goal was to simplify this so the user did not have to type in PowerShell.  At this point, I would actually have the user use PowerShell and turn this script into a cmdlet in an auto-loading module.  To do this new process via batch file, here are the steps:
1.       Open Notepad
2.       Open the batch file in notepad
3.       Manually enter the computer name.
4.       Save the file
5.       Double click the desk shortcut to the batch file.

If this was a cmdlet in an auto-loading module, here is the process:
1.       Open PowerShell
2.       Type CmdletName–ComputerName INDY-DC1
That is it!




Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Here is one from this week’s PowerShell class. We just finished a lesson on methods and I passed on “The first rule of Computer Science” to my class that one of my professions, Dan Matthews, passed on to me.  It simply states “Never re-invent the wheel”.  With that, we started to talk about the value of methods.  The question popped us as to which version of .Net is PowerShell using and how to select a different version?  Well, here is how to determine the current installed versions of .Net:

 PS C:\> Get-Childitem "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP"


    Hive: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP


Name                           Property                                                                                                                            
----                           --------                                                                                                                            
CDF                                                                                                                                                                 
v2.0.50727                     CBS       : 1                                                                                                                       
                               Increment : 4927                                                                                                                     
                               Install   : 1                                                                                                                        
                               OCM       : 1                                                                                                                       
                               SP        : 2                                                                                                                        
                               Version   : 2.0.50727.4927                                                                                                           
v3.0                           CBS       : 1                                                                                                                       
                               Increment : 4926                                                                                                                     
                               Install   : 1                                                                                                                       
                               SP        : 2                                                                                                                       
                               Version   : 3.0.30729.4926                                                                                                           
v3.5                           CBS         : 1                                                                                                                     
                               Install     : 1                                                                                                                     
                               InstallPath : C:\Windows\Microsoft.NET\Framework64\v3.5\                                                                             
                               SP          : 1                                                                                                                     
                               Version     : 3.5.30729.4926                                                                                                        
v4                                                                                                                                                                  
v4.0                           (default) : deprecated                                                                                                              



As for how to select a different version, I am going to default over to an article on Kris Powell’s blog.  You can view it here https://www.pdq.com/blog/powershell-running-net-4-with-powershell-v2/

Remember the 1st rule of computer science.  He answered it, let’s not repeat his work and give him the proper credit.


Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
I’m currently in the middle of writing version 2 of my Security+ learning engine.  Some of you from last weeks Security+ class know that I have been developing a tool using SAPIEN PowerShell Studio to help you with the massive amount of terminology that you need to know for the Sec+ exam.  You also remember that I was putting in some safe guards to help protect the application from piracy.  I’m going to share one of those safeguards.  Here is a current view of version 2 of the product.


What I want to do is to disable the ability to copy the questions and answers to the clipboard.  Here is how you do it.  In the Designer view, click on the object that you want to protect.  In this case, I am clicking the text box that contains the questions.  In the Properties dialog, set the value for ShortcutsEnabled to False.  This turns off the right clicking capability of the object.



While talking about Active Directory Rights Management in the past, I’ve been hit hard that I cannot stop someone for taking out their cell phone and taking a picture.  Yes, you are right, I cannot stop a determined attacker, but I can make it more difficult.
Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Jason Yoder, MCT by Jason Yoder, Mct - 4M ago
This week we had a little surprise when we were working on the auditing component of our Security+ class here in North Carolina.  The labs this week are built in Azure and I gave each one a public IP address.  On Wednesday afternoon with the VMs online since Monday, we took a look at the failure login attempts.  We got a big surprise with over 11,000 bad logon attempts. 

We then started the second set of VMs fresh.  It took about 10 minutes until we started to see the attempts to access those VMs.  If this does not tell you we operate in a hostile environment, nothing will.  Here is the PowerShell code that we used and the results on the systems online for 10 minutes.

Get-EventLog -LogNameSecurity -InstanceId4625 |
    Select-Object-Property TimeGenerated,
    @{N="AccountName";E={$_.Message.Split("`n")[12].Replace("Account Name:",$Null).Trim()}},
    @{N="Domain";E={$_.Message.Split("`n")[13].Replace("Account Domain:",$Null).Trim()}},
    @{N="Source";E={$_.Message.Split("`n")[26].Replace("Source Network Address:   ",$Null).Trim()}} 

TimeGenerated        AccountName   Domain Source       
-------------        -----------   ------ ------       
1/10/2018 6:23:36 PM administrator adatum 12.220.254.194
1/10/2018 6:20:09 PM Administrator Adatum -            
1/10/2018 6:20:09 PM Administrator Adatum -            
1/10/2018 6:13:07 PM ADMINISTRATOR        208.110.81.186
1/10/2018 6:12:52 PM ADMINISTRATOR        86.109.122.130
1/10/2018 6:08:53 PM -             -      -            
1/10/2018 6:08:53 PM -             -      -            
1/10/2018 6:05:54 PM Administrator Adatum -            
1/10/2018 6:05:54 PM Administrator Adatum -            
1/10/2018 2:37:23 PM Administrator Adatum -            
1/10/2018 2:37:23 PM Administrator Adatum -  

Let’s find out how many bad logon attempts there were.

Get-EventLog -LogNameSecurity -InstanceId4625 |
    Select-Object-Property TimeGenerated,
    @{N="AccountName";E={$_.Message.Split("`n")[12].Replace("Account Name:",$Null).Trim()}},
    @{N="Domain";E={$_.Message.Split("`n")[13].Replace("Account Domain:",$Null).Trim()}},
    @{N="Source";E={$_.Message.Split("`n")[26].Replace("Source Network Address:   ",$Null).Trim()}} |
    Measure-object

Count    : 13486
Average  :
Sum      :
Maximum  :
Minimum  :
Property : 


Here is the code to provide a list of all attempted user names.

Get-EventLog -LogNameSecurity -InstanceId4625 |
    Select-Object-Property TimeGenerated,
    @{N="AccountName";E={$_.Message.Split("`n")[12].Replace("Account Name:",$Null).Trim()}},
    @{N="Domain";E={$_.Message.Split("`n")[13].Replace("Account Domain:",$Null).Trim()}},
    @{N="Source";E={$_.Message.Split("`n")[26].Replace("Source Network Address:   ",$Null).Trim()}} |
    Select-object-Property AccountName-Unique |
    Sort-Object-Property AccountName


There was 2137 as of the writing of this article.



Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Many of you who read this blog know that I utilize SAPIEN PowerShell Studio when I need a GUI on top of my code.  Here is the latest.  As part of my delivery of Security+ for both the Navy and my civilian business, one item of concern that I hear time and time again is the sheer amount of terminology that students need to memorize.  On the Navy side, we only give them a week to take the course and prep for the exam, which is a huge undertaking.  To help address this issue, I used PowerShell Studio to create a testing engine.


I have always told my PowerShell classes that if the tool does not exist, make it.  This tool continuously repeats questions until the student gets it right.  With several hundred test questions in the pool for the 11 modules that I teach, this test environment is part of the Azure lab environment that I have for each student.  There is another tool to help populate the test engine with questions.  without PowerShell Studio, this would have been very difficult to create with the PowerShell ISE.


Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

This is often a source of confusion when someone is new to PowerShell.  Since PowerShell is used mostly be non-programers, they often do not understand what an object is.  You need to have a basic understanding of objects to know how these two difference parameters work.  Let me give you an example.
PS C:\> Get-ADUser -Identity AdminUser
 
 
DistinguishedName : CN=AdminUser,CN=Users,DC=Adatum,DC=com
Enabled           : True
GivenName         :
Name              : AdminUser
ObjectClass       : user
ObjectGUID        : 696591fc-6697-4d93-b624-3ef7de206ee9
SamAccountName    : AdminUser
SID               : S-1-5-21-817349643-1871075972-606077769-500
Surname           :
UserPrincipalName :
 
 
Here you can see a subset of a user object.  This object is displaying 10 properties in the left hand column with their associated values on the right.  Let’s call the Get-Type() method to see the object’s typename.
 
PS C:\> (Get-ADUser -Identity AdminUser).getType()
 
IsPublic IsSerial Name    BaseType                                                                       
-------- -------- ----    --------                                                                      
True     False    ADUser  Microsoft.ActiveDirectory.Management.ADAccount 
 
We can see that this is an ADUser object.  Pipe it to Get-Member
    TypeName: Microsoft.ActiveDirectory.Management.ADUser
 
Name              MemberType            Definition                                                                                       
----              ----------            ----------                                                                                       
Contains          Method                bool Contains
Equals            Method                bool Equals(S
GetEnumerator     Method                System.Collec
GetHashCode       Method                int GetHashCo
GetType           Method                type GetType(
ToString          Method                string ToStri
Item              ParameterizedProperty Microsoft.Act
DistinguishedName Property              System.String
Enabled           Property              System.Boolea
GivenName         Property              System.String
Name              Property              System.String
ObjectClass       Property              System.String
ObjectGUID        Property              System.Nullab
SamAccountName    Property              System.String
SID               Property              System.Securi
Surname           Property              System.String
UserPrincipalName Property              System.String
 
Notice again that the entire object is a Microsoft.ActiveDirectory.Management.ADUserobject and the property called Nameis a System.String object.  Let’s explore the –Property parameter first.
PS C:\> Get-ADUser -Identity AdminUser | Select-Object -Property Name
 
Name    
----    
AdminUser
 
Now let’s pass this object to Get-Member.
PS C:\> Get-ADUser -Identity AdminUser | Select-Object -Property Name | Get-Member
 
 
   TypeName: Selected.Microsoft.ActiveDirectory.Management.ADUser
 
Name        MemberType   Definition                   
----        ----------   ----------                   
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()            
GetType     Method       type GetType()               
ToString    Method       string ToString()            
Name        NoteProperty string Name=AdminUser     
 
Notice the TypeName.  It is still a Microsoft.ActiveDirectory.Management.ADUserobject, but it is prefixed with Selected.  It is only containing selected information from the original object.  You still see a property called Name with a typename of System.String.  Now let’s look at –ExpandProperty.
PS C:\> Get-ADUser -Identity AdminUser | Select-Object -ExpandProperty Name
AdminUser
 
You see the user name, but you do not see a column header.  Piping the object to Get-Member reveals the reason why:
PS C:\> Get-ADUser -Identity AdminUser | Select-Object -ExpandProperty Name | Get-Member
 
 
   TypeName: System.String
 
Name             MemberType            Definition                                                                                         
----             ----------            ----------                                                                                        
Clone            Method                System.Object
CompareTo        Method                int CompareTo(
Contains         Method                bool Contains(
CopyTo           Method                void CopyTo(in
EndsWith         Method                bool EndsWith(
Equals           Method                bool Equals(Sy
GetEnumerator    Method                System.CharEnu
GetHashCode      Method                int GetHashCod
GetType          Method                type GetType()
GetTypeCode      Method                System.TypeCod
IndexOf          Method                int IndexOf(ch
IndexOfAny       Method                int IndexOfAny
Insert           Method                string Insert(
IsNormalized     Method                bool IsNormali
LastIndexOf      Method                int LastIndexO
LastIndexOfAny   Method                int LastIndexO
Normalize        Method                string Normali
PadLeft          Method                string PadLeft
PadRight         Method                string PadRigh
Remove           Method                string Remove(
Replace          Method                string Replace
Split            Method                string[] Split
StartsWith       Method                bool StartsWit
Substring        Method                string Substri
ToBoolean        Method                bool IConverti
ToByte           Method                byte IConverti
ToChar           Method                char IConverti
ToCharArray      Method                char[] ToCharA
ToDateTime       Method                datetime IConv
ToDecimal        Method                decimal IConve
ToDouble         Method                double IConver
ToInt16          Method                int16 IConvert
ToInt32          Method                int IConvertib
ToInt64          Method                long IConverti
ToLower          Method                string ToLower
ToLowerInvariant Method                string ToLower
ToSByte          Method                sbyte IConvert
ToSingle         Method                float IConvert
ToString         Method                string ToStrin
ToType           Method                System.Object
ToUInt16         Method                uint16 IConver
ToUInt32         Method                uint32 IConver
ToUInt64         Method                uint64 IConver
ToUpper          Method                string ToUpper
ToUpperInvariant Method                string ToUpper
Trim             Method                string Trim(Pa
TrimEnd          Method                string TrimEnd
TrimStart        Method                string TrimSta
Chars            ParameterizedProperty char Chars(int
Length           Property              int Length {gePS C:\> Get-ADUser -Identity
 
We are no longer working with the original Microsoft.ActiveDirectory.Management.ADUserobject.  We are working with System.String. –ExpandProperty pulled the value of the Name property out of the original object and discarded the original object.  It them placed the value of the Name property in the PowerShell pipeline.  This was a System.String object.
 
In summary, -Propertyremoved members from an object that you do not want to use and can accept multiple values.  –ExpandProperty removes the original object, but allows the value of the single specified property to continue.
 
 
 
 
 
 
 
Read Full Article
Visit website
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Today we finished up one of my advanced PowerShell classes.  What was unique about this one is that I ran the labs using Azure which gave the labs internet access.  Because of this, we were able to play around a little bit more and access our PSWA sites on our phones.  That was one cool thing, but the other thing that I never noticed is the ability to have a disconnected session.


Here is our current session:

Notice that I created a variable of $X = 5.  To disconnect, just simply click Save instead of Exit.

Open your web browser again and sign into PowerShell Web Access with the same credentials.  Here is what you get:
From here you can do the following:
  •         Check the current session and select Delete.  This will close the disconnected session.
  •         Click New Session to open a fresh session. 
  •         ClickPSWA1.  This is your disconnected session. (You can have multiple disconnected sessions.)

Notice that when you run Get-Variable, your X variable is still present:


Also your history list is preserved.


It’s the little things that you notice from time to time that keeps things interesting.
Read Full Article
Visit website

Read for later

Articles marked as Favorite are saved for later viewing.
close
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview