Skip to content

Instantly share code, notes, and snippets.

@phyoewaipaing
Last active November 12, 2023 13:58
Show Gist options
  • Save phyoewaipaing/50dc83e8c41d4800a09e38ae0fac710e to your computer and use it in GitHub Desktop.
Save phyoewaipaing/50dc83e8c41d4800a09e38ae0fac710e to your computer and use it in GitHub Desktop.
Create FTP with local user isolation
<#
.SYNOPSIS
Script that will setup FTP with multiple local user isolation.
.DESCRIPTION
** This script will setup the windows FTP with multiple user isolation. It will prompt you to install necessary roles to setup FTP.
** It will create necessary physical directory under ftp root directory and virtual directories under the 'LocalUser' folder which is needed to setup FTP with user isolation.
SSL is disabled by default, but you can turn it on in the installation parameter or later.
** You can choose whether to create an FTP admin account, which can administratively browse every users' folder.
** You need to provide CSV (Comma-Separated-Value) file with the these headers (see the examples below).
Username, Description, Password
Mr. Jack, Sales Executive,SDF@$#%DSF
Mr. Tom, Branch Manager,@#$SFSDFsdf
Example usage:
Create_FTP_Local_User_Isolation.ps1 -FtpSiteName 'The Magic Script Studio' -Port 21 -FtpRootDir 'C:\UsersFTP' -AdminIncluded -UserListCsv 'c:\userlist.csv'
Author: Phyoe Wai Paing
Country: Myanmar(Burma)
Released: 12/23/2016
.EXAMPLE
Create_FTP_Local_User_Isolation.ps1 -FtpSiteName 'The Magic Script FTP' -Port 21 -FtpRootDir 'C:\UsersFTP' -AdminIncluded -RequireSSL $false -UserListCsv 'c:\userlist.csv'
This will create FTP site with name 'The Magic Script' with port number 21. The FTP root location is 'C:\UsersFTP' & an ftp administrator account will be created.
This ftp server will SSL connection and you will need to configure SSL certificate in the IIS console after setup. The users to be created are listed in the csv file 'c:\userlist.csv'.
.EXAMPLE
Create_FTP_Local_User_Isolation.ps1
Running this script without parameters will setup FTP with user isolation with default values. The ftp site name is 'First-Ftp-Site' with port 21 in the 'C:\ftproot' physical directory.
The ftp administrator account will not be created. The ftp connection will not use SSL certificate. The list of ftp users to be created is read from 'c:\userlist.csv'
.PARAMETER FtpSiteName
This specify the FTP site name you want to create.
.PARAMETER Port
This specify the TCP port the ftp service will be listening.
.PARAMETER FtpRootDir
This specify the ftp root directory. If you create the
.PARAMETER AdminIncluded
This specify to create ftp admin account to browse all users' uploaded files.
.PARAMETER RequireSSL
This specify to setup FTP with SSL connection. You will need to specify/import a valid certificate in IIS console after the setup.
.PARAMETER UserListCsv
This specify the list of users to be created in csv format.
.LINK
You can find this script and more at: https://www.scriptinghouse.com/
#>
param( [string]$FtpSiteName='First-Ftp-Site',[int]$Port=21,[string]$FtpRootDir='c:\ftproot',[switch]$AdminIncluded=$False,[switch]$RequireSSL=$false,[string]$UserListCsv='UserList.csv')
############# Prompt for userlist.csv file until file is found & exported as csv #####################
Do
{
Try {
$User_List = Import-Csv $UserListCsv
$CsvFileFound = 1;
}
catch {
$UserListCsv = Read-Host "Cannot find the 'UserList.csv' file for multiple users' creation. Please specify the file full path?"
$CsvFileFound = 0;
}
} while ($CsvFileFound -eq 0)
############# If the user specify to create FTP administrator account, then prompt for the credential #####################
If ($AdminIncluded)
{
$FtpAdminCred = Get-Credential('FtpAdmin') -Message "Please provide usename & password for creating FTP Admin account."
Try {
[string]$AdminName = $FtpAdminCred.GetNetworkCredential().Username
[string]$AdminPassword = $FtpAdminCred.GetNetworkCredential().Password
}
Catch {
Write-Host -Fore Red "Please specify the FTP Admin's name & Password. Now the script exits."; Exit;
}
}
############# Install the required windows features if not already installed ###############################
$RequiredRoles = Get-WindowsFeature Web-Ftp-Service,Web-Scripting-Tools,Web-Mgmt-Console, WAS-Process-Model
$NotInstalledRoles=@();
$RequiredRoles | where { $_.Installed -ne $True } | foreach {
Write-Host "$($_.DisplayName) is NOT installed";
$NotInstalledRoles +=$_.Name
}
If ($NotInstalledRoles.count)
{
[console]::foregroundcolor="yellow"
$InstallRequiredRoles = Read-Host "Some required roles are not installed. Do you want to install them (y/n)?"
while($InstallRequiredRoles -ne 'y' -AND $InstallRequiredRoles -ne 'n')
{
Write-Host "Please only type 'y' or 'n'."
$InstallRequiredRoles = Read-Host "Some required roles are not installed. Do you want to install them (y/n)?"
}
[console]::foregroundcolor="white"
If ($InstallRequiredRoles -eq 'y')
{
Add-WindowsFeature $NotInstalledRoles | Out-Null
}
else
{
Write-Host "Setup cannot continue if you do not install necessary roles. Now exits."
Exit;
}
}
############ Load the module, if not already loaded #####################################################
If ((Get-Module -Name WebAdministration).Name -ne "WebAdministration")
{
Try {
Import-Module -Name WebAdministration -EA Stop
}
catch {
Write-Host -Fore Red "WebAdministration Module is not loaded. Please make sure you have installed necessary Windows Server Features for FTP Services."
}
}
############# Data Lookup from the output of $SslControlModeString for user display #########################
$SslMode = DATA { ConvertFrom-StringData -StringData @'
SslAllow = "Allow SSL Connection"
SslRequire = "Require SSL Connection"
'@}
############### Read the IIS configuration file as xml format ########################################
$AppHostConfig = $Null
Try { $AppHostConfig = [xml](Get-Content $env:windir\system32\inetsrv\config\applicationHost.config -EA stop ) }
catch { "AppHostConfig.config file does not exist" }
############## Continue the rest of the operation only if the IIS config exists #############################
If ($AppHostConfig -is [xml])
{
################# Check if the ftp site already exists, if not create ftp site with necessary information #############
If (Test-Path "IIS:\Sites\$FtpSiteName\LocalUser")
{
$FtpSiteState = (Get-Item IIS:\Sites\$FtpSiteName\).state
Write-Host "FTP site is already setup. FTP Site state is $FtpSiteState."
}
else
{
################ Create the FTP root directory, errors are caught & displayed ############
New-Item -Type Directory -Path $FtpRootDir -ErrorVariable CreateDirError -EA SilentlyContinue | Out-Null;
If ($CreateDirError[0].Exception )
{
Write-Host -fore Red "$CreateDirError"
If (!(Test-Path $FtpRootDir))
{
Exit; ## Exit the script if the Ftp root directory is not created.
}
}
else
{
Write-Host -Fore Green "Directory $FtpRootDir created successfully."
}
################# Create FTP site with necessary information ######################
Try {
New-Item "IIS:\Sites\$FtpSiteName" -bindings @{protocol="ftp";bindingInformation="`:$Port`:"} -physicalPath $FtpRootDir -EA:"Stop" ##Create New Ftp Site with binding information, -ErrorAction:"Stop" is used to fetch catch statement
Write-Host -Fore Green "FTP Site `'$FtpSiteName`' is successfully created."
If ($Result.State -eq "Stopped")
{
Write-Host -Fore Red "FTP Service cannot be started. Please make sure there is no other service using Port:$Port"
}
}
Catch {
Write-Host -Fore Cyan "FTP Site `($FtpSiteName`) already exists. No further action is taken."
}
################# Create 'LocalUser' Directory for FTP with user isolation #########
New-Item "IIS:\Sites\$FtpSiteName\LocalUser" -physicalPath $FtpRootDir -type VirtualDirectory | Out-Null
Try {
Set-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.security.authentication.basicAuthentication.enabled -value $true -EA:"Stop"
}
Catch {
Write-Host -Fore Red "Cannot enable the Basic Authentication for $FtpSiteName."
}
}
############## If the user isolation mode is not correct, then change FTP User Isolation Mode to 3 #############
If ((Get-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.userIsolation.mode) -ne "IsolateAllDirectories")
{
Try {
Set-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.userIsolation -Value 3
Write-Host -Fore Green "User Isolation Mode is successfully changed to `"User name directory(Disable global virtual directories)`"."
}
Catch {
Write-Host -Fore Red "Cannot change User Isolation Mode to `"User name directory(Disable global virtual directories)`"."
}
}
else
{
Write-Host -Fore Cyan "User Isolation Mode is already in `"User Name Directory Isolation Mode`". No Need to change."
}
############### Get current FTP SSL Mode ##################
$SslControlModeString = Get-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.security.ssl.controlChannelPolicy
$SslDataModeString = Get-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.security.ssl.dataChannelPolicy
################ The following condition will test if the current status of FTP SSL and the wanted SSL state(from $RequireSSL Flag) is matched or not. If not matched, it will change the SSL Mode ############
If(($RequireSSL -AND (($SslControlModeString -eq "SslAllow") -Or ($SslDataModeString -eq "SslAllow"))) -OR ((!$RequireSSL) -AND (($SslControlModeString -eq "SslRequire") -Or ($SslDataModeString -eq "SslRequire"))))
{
Write-Host -Fore Yellow "Current FTP SSL Connection Mode for `'$FtpSiteName`' is $($SslMode[$SslControlModeString])"
Try {
Set-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.security.ssl.controlChannelPolicy -Value $(if($RequireSSL){ 1 } else {0}) ## If $Require flag is set, then change ftp control channel ssl connection to "Require SSL" Mode, else Allow SSL Mode
Set-ItemProperty "IIS:\Sites\$FtpSiteName" -Name ftpServer.security.ssl.dataChannelPolicy -Value $(if($RequireSSL){ 1 } else {0}) ## If $Require flag is set, then change ftp data channel ssl connection to "Require SSL" Mode, else Allow SSL Mode
Write-Host -Fore Green "FTP SSL Connection Mode for `'$FtpSiteName`' is successfully set to `"$(If($RequireSSL){"Require SSL Connection"}else{"Allow SSL Connection"})`""
}
Catch {
Write-Host -Fore Red "Cannot change FTP Connection to `"$(If($RequireSSL){"Require SSL Connection"}else{"Allow SSL Connection"})`""
}
}
else
{
Write-Host -Fore Cyan "Current FTP SSL Connection Mode for `'$FtpSiteName`' is $($SslMode[$SslControlModeString]). No Need to change."
}
$LocalUsers = (Get-WmiObject -class Win32_UserAccount).Name ## Get local users to check before creating new users
############## If $AdminName Flag is set, then FTP Admin is given permission to browse the whole ftp site ##############
If ($AdminName)
{
#$FtpAdminCreated = 0;
############## Create FTP Admin user account with password complexity checked with try/catch statement #############
If( !($LocalUsers -match $AdminName))
{
$Computer = [ADSI]"WinNT://$Env:COMPUTERNAME,Computer" ## Create directory Service Instance to make connection to local computer
$user = $Computer.Create("User","$AdminName")
$user.SetPassword($AdminPassword)
Try {
$user.SetInfo()
$user.UserFlags = 64 + 65536
$user.SetInfo()
$user.Description = "FTP Admin to browse uploaded files"
$user.SetInfo()
Write-Host -fore Green "The FTP Admin account `'$AdminName`' has been successfully created."
$FtpAdminCreated = 1;
}
catch {
Write-Host -Fore Red "Blank password or Weak password detected for `'$AdminName`'. FTP administrator `'$AdminName`' is not created."
}
}
else
{ Write-Host -fore cyan "The FTP Administrator account already exists."}
############# If FTP admin is created, then add Read/Write Permission for FTP admin to FTP Site ########################
Try {
Add-WebConfiguration -Filter /System.FtpServer/Security/Authorization -Value (@{AccessType="Allow"; Users="$AdminName"; Permissions="Read, Write"}) -PSPath "IIS:\" -Location "$FtpSiteName"
Write-Host -Fore Green "`"$AdminName`" is given Read,Write Permission to $FtpSiteName successfully.`n"
}
Catch [System.Runtime.InteropServices.COMException]
{
Write-Host -Fore Cyan "Read,Write permission for `"$AdminName`" to $FtpSiteName already has been set. No Need to change."
}
############# If FTP admin is created, you will also need to create virtual directory for FTP admin ##########################
If (!(($AppHostConfig.selectNodes('//application') | where { $_.InnerXML -Like "*$FtpRootDir*" }).virtualDirectory.path | where { $_ -match $AdminName }) )
{
New-Item "IIS:\Sites\$FtpSiteName\LocalUser\$AdminName" -physicalPath "$FtpRootDir" -type VirtualDirectory | Out-Null
Write-Host -Fore Green "Virtual Directory for `'$AdminName`' created successfully."
}
}
Write-Host -fore yellow "Creating FTP users, physical directories & virtual directories..."
$ExtraUsers = 0; ## Initially set the Extra users Value to 0;
$UsersCreateError = 0; ## Initially set the error flag to zero in creating users & physical/virtual directories
$Computer = [ADSI]"WinNT://$Env:COMPUTERNAME,Computer" ## Create directory Service Instance to make connection to local computer
$User_List | foreach {
$CurrentUserName = $_.username
$Password = $_.password
$Description = $_.description
############## Create Local users if the user is not already created , password complexity checked with try/catch statement #############
If (! ($LocalUsers -match $CurrentUserName))
{
$User_Created = 0; ## Initially Set the $User_Created Flag to zero
$user = $Computer.Create("User","$CurrentUserName")
$user.SetPassword($Password)
Try {
$user.SetInfo()
$user.UserFlags = 64 + 65536
$user.SetInfo()
$user.Description = $Description
$user.SetInfo()
Write-Host -fore Green "User `'$CurrentUserName`' created successfully."
$User_Created = 1;
}
catch
{
Write-Host -fore Red "Blank password or Weak password detected for `'$CurrentUserName`'. Change the password with strong one & run the script later. User is NOT created."
$UsersCreateError = 1;
}
}
else
{
Write-Host -fore Cyan "User `'$CurrentUserName`' already created."
}
############# Check if the physical directory is created for each user, if not, create the folder under FTP root directory if the user is just created or alredy created before the script run #################
If ((!(Test-Path "$FtpRootDir\$CurrentUserName") -AND $User_Created ) -OR (!(Test-Path "$FtpRootDir\$CurrentUserName") -AND ($LocalUsers -match $CurrentUserName) ) )
{
New-Item -Type Directory -Path "$FtpRootDir\$CurrentUserName" -ErrorVariable CreateDirError -EA SilentlyContinue | Out-Null ## Create physical directory with the user name
If ($CreateDirError[0].Exception )
{
$CreateDirError[0].Exception
}
else
{
Write-Host -Fore Green "Physical Directory for `'$CurrentUserName`' created successfully."
}
}
elseif (!$User_Created -AND !(Test-Path "$FtpRootDir\$CurrentUserName"))
{
Write-Host -fore Red "Cannot create Physical Directory for `'$CurrentUserName`'. User `'$CurrentUserName`' is not created earlier."
$UsersCreateError = 1;
}
else
{
Write-Host -fore Cyan "Physical Directory for `'$CurrentUserName`' already exists."
}
############### Check in the XML config file that whether the virtual directory node already exists #################
If ( !(($AppHostConfig.selectNodes('//application') | where { $_.InnerXML -Like "*$FtpRootDir*" }).virtualDirectory.path | where { $_ -match $CurrentUserName }) )
{
############## Check if the current user is just created or physical directory already exists (even though the user has been created before the script run) ######################
If ($User_Created -OR (!$User_Created -AND (Test-Path "$FtpRootDir\$CurrentUserName" )))
{
New-Item "IIS:\Sites\$FtpSiteName\LocalUser\$CurrentUserName" -physicalPath "$FtpRootDir\$CurrentUserName" -type VirtualDirectory | Out-Null
Write-Host -Fore Green "Virtual Directory for `'$CurrentUserName`' created successfully."
}
elseif ( !$User_Created -AND !(Test-Path "$FtpRootDir\$CurrentUserName" ))
{
Write-Host -Fore Red "Cannot create Virtual Directory for `'$CurrentUserName`'. User is not created earlier. "
$UsersCreateError = 1;
}
else
{
Write-Host -fore Cyan "Virtual Directory `'$CurrentUserName`' already exists under $FtpSiteName\LocalUser. Permission will be added to this directory."
}
}
############## Check for each user that only the current user is given permission to current virtual directory ############################
If ( $AuthorizationRules = ($AppHostConfig.SelectNodes("//location") | where { $_.path -eq "$FtpSiteName/LocalUser/$CurrentUserName" })."system.ftpserver".security.authorization)
{
$AuthorizationRules.selectnodes('add') | where { $_.users -ne $CurrentUserName } | foreach { if ($_)
{ write-host -fore yellow "Extra users Detected: $($_.users) has $($_.AccessType) $($_.permissions) permissions to $FTPSiteName/LocalUser/$CurrentUserName";
$ExtraUsers ++; } }
}
}
############## If there are any users already created which are listed on userlist.txt file, then write-host the verbose output ##################
$UserNameList = $User_List.username ## Get only the name of user lists (to compare with the local accounts on the system)
If (($LocalUsers | foreach { $UserNameList -match $_ }).count)
{
Write-Host -fore Yellow "Checking users' permission to each virtual directory..."
}
############## If extra users are detected, then prompt for user permission deletion #################################################
If ($ExtraUsers)
{
[console]::foregroundcolor="yellow"
$Remove_Extra_Users = Read-Host "Multiple users are given permission to each virtual directory. Do you want to remove these users (y/n)?"
while($Remove_Extra_Users -ne 'y' -AND $Remove_Extra_Users -ne 'n')
{
Write-Host "Please only type 'y' or 'n'."
$Remove_Extra_Users = Read-Host "`nMultiple users are given permission to each virtual directory. Do you want to remove these users (y/n)?"
}
[console]::foregroundcolor="white"
############# If the user choose 'y', then delete the extra users' permission on each virtual directory as from authorization rules ##############
If ($Remove_Extra_Users -eq 'y')
{
$AppHostConfig = [xml](Get-Content $env:windir\system32\inetsrv\config\applicationHost.config -EA stop ) ## Re-read the configuration file to get the latest config update (for eg; if the virtual directory is created)
$User_List | foreach {
$CurrentUserName = $_.username
$AuthorizationRules = ($AppHostConfig.SelectNodes("//location") | where { $_.path -eq "$FtpSiteName/LocalUser/$CurrentUserName" })."system.ftpserver".security.authorization
If ( $AuthorizationRules )
{
$AuthorizationRules.selectnodes('add') | where { $_.users -ne $CurrentUserName } | foreach { $_.ParentNode.RemoveChild($_) } | Out-Null
}
}
$AppHostConfig.Save("$env:windir\system32\inetsrv\config\applicationHost.config") ## Save the IIS configuration file
Write-Host "Extra users' permissions have been removed and saved to AppHostConfig.config file"
}
}
$AppHostConfig = [xml](Get-Content $env:windir\system32\inetsrv\config\applicationHost.config -EA stop ) ## Re-read the configuration file if the virtual directory is created
$NewLocalUsers = (Get-WmiObject -class Win32_UserAccount).Name ## Get newly create users (needed to add permission on the next section)
############## Add the Read/Write permission to each virtual directory for each user as an authorization rule ############################
$User_List | foreach {
$CurrentUserName = $_.username
############## If the Current user is already created, then add the permission for that user to the virtual directory #########################
If ($NewLocalUsers -Contains $CurrentUserName)
{
$AuthorizationRules = ($AppHostConfig.SelectNodes("//location") | where { $_.path -eq "$FtpSiteName/LocalUser/$CurrentUserName" })."system.ftpserver".security.authorization
############# If the authorization child node(s) exists & if the intended user is included in authorized users already, then skip adding permission ########
If ($AuthorizationRules -AND ($AuthorizationRules.selectnodes('add') | where { $_.users -eq $CurrentUserName }).Users -Contains $CurrentUserName )
{
Write-Host -fore cyan "User `'$CurrentUserName`' already has read/write permission to `'$CurrentUserName`' Virtual Directory."
}
else
{
Add-WebConfiguration -Filter /System.FtpServer/Security/Authorization -Value (@{AccessType="Allow"; Users="$CurrentUserName"; Permissions="Read, Write"}) -PSPath "IIS:\" -Location "$FtpSiteName/LocalUser/$CurrentUserName"
}
}
}
############## If there are any users already created which are listed on userlist.txt file, then write-host the verbose output #################
If( ($LocalUsers | foreach { $UserNameList -match $_ }).count -AND $UsersCreateError )
{
Write-Host -fore yellow "FTP user isolation is completed with some errors. Please correct errors & re-run the script."
Try {
start-service FTPSVC -EA Stop
Write-Host -Fore Green "FTP service successfully restarted."
}
catch {
Write-Host "Cannot restart the FTP Service. Please check the service start-up type."
}
}
elseIf (!$UsersCreateError)
{
Write-Host -Fore Green "All users' permissions are correct.`nFTP Site with User Isolation has been successfully setup."
Try {
Restart-service FTPSVC -EA Stop
Write-Host -Fore Green "FTP service successfully restarted."
}
catch {
Write-Host "Cannot restart the FTP Service. Please check the service start-up type."
}
}
else
{
Write-Host -fore Yellow "Errors occurred. One or more users are not created. Please correct errors & re-run the script."
}
}
else
{
Write-Host -fore red "ApplicationHostConfig.config file does not exist in $env:windir\system32\inetsrv\config\ Directory"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment