I have created this PowerShell script for unattended installation & configuration of FTP server on Windows Server Core edition.
This script will:
- ask for FTP site name, READ and WRITE FTP user
- install FTP, IIS roles
- create FTP site
- create READ and WRITE users and grant them permissions to IIS
- set Window Updates
- if finds RAW disk, format and assign E: letter and use it as FTP root otherwise 'C:\FTP_Root'
- in FTP root creates WRITE and READ folders and set appropriate NTFS permissions
- change ports for passive FTP to 60000-65535
- enable FTPS (using self-signed certificate)
- enable FTP on firewall
Maybe it will help somebody...👍
Script content:
<
script will set up new Windows Server as FTP server:
- install FTP, IIS roles
- create FTP site
- create READ and WRITE users and grant them permissions to IIS
- set Window Updates
- if finds RAW disk, format and assign E: letter and use it as FTP root otherwise 'C:\FTP_Root'
- in FTP root creates WRITE and READ folders and set appropriate NTFS permissions
- change ports for passive FTP to 60000-65535
- enable FTPS (using self-signed certificate)
- enable FTP on firewall
inspired by:
https://blogs.iis.net/jaroslad/windows-firewall-setup-for-microsoft-ftp-publishing-service-for-iis-7-0
http://fabriccontroller.net/passive-ftp-and-dynamic-ports-in-iis8-and-windows-azure-virtual-machines/
https://4sysops.com/archives/install-and-configure-an-ftp-server-with-powershell/
param (
$FTPSiteName = (Read-Host 'Enter FTP site name')
,
$readFTPuser = (Read-Host "Enter name of READ FTP account")
,
$writeFTPuser = (Read-Host "Enter name of WRITE FTP account")
)
if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
throw "Run with administrator rights"
}
Write-Warning "Public IP of this server has to remain the same, so in AWS use elastic IP, otherwise after each start, VM gets new IP!!!`n"
Write-Warning "For FTP in passive mode to work correctly, public IP of this computer will be set in IIS. In case this computer doesn't have it's final IP (you will set Elastic IP etc), DO NOT CONTINUE!"
$choice = ""
while ($choice -notmatch "^[Y|N]$") {
$choice = Read-Host "Continue? (Y|N)"
}
if ($choice -eq "N") {
break
}
$dnsName = Read-Host "Enter DNS name of this FTP server (e.g. ftp.contoso.com). It will be used for certificate creation."
$readFTPuser, $writeFTPuser | % {
$Password = Read-Host "Password for '$_' user account" -AsSecureString
$null = New-LocalUser $_ -Password $Password -FullName $_ -Description $_
$null = Set-LocalUser -Name $_ -PasswordNeverExpires:$true -UserMayChangePassword:$false
}
Add-LocalGroupMember -Group "IIS_IUSRS" -Member $writeFTPuser
$scriptBlock = {
$ScanResult = Invoke-CimMethod -Namespace "root/Microsoft/Windows/WindowsUpdate" -ClassName "MSFT_WUOperations" -MethodName ScanForUpdates -Arguments @{SearchCriteria = "IsInstalled=0 AND Type='Software'" }
if ($ScanResult.Updates) {
$result = Invoke-CimMethod -Namespace "root/Microsoft/Windows/WindowsUpdate" -ClassName "MSFT_WUOperations" -MethodName InstallUpdates -Arguments @{Updates = $ScanResult.Updates }
}
$pendingReboot = Invoke-CimMethod -Namespace "root/Microsoft/Windows/WindowsUpdate" -ClassName "MSFT_WUSettings" -MethodName IsPendingReboot | select -exp pendingReboot
if ($pendingReboot) {
shutdown /r /t 30 /c "restarting because of newly installed updates"
}
}
$bytes = [System.Text.Encoding]::Unicode.GetBytes($scriptBlock.ToString())
$encodedString = [Convert]::ToBase64String($bytes)
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-ExecutionPolicy Bypass -NoProfile -encodedcommand $encodedString"
$trigger = New-ScheduledTaskTrigger -Daily -At 3am
Register-ScheduledTask -User "SYSTEM" -Action $action -Trigger $trigger -TaskName "WindowsUpdate" -Description "regular updating of Windows" -Force
$rawDisk = Get-Disk | ? { $_.partitionstyle -eq "raw" }
$rawDisk | Initialize-Disk -PartitionStyle MBR
$rawDisk | New-Partition -DriveLetter E -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "DATA" -Confirm:$false
Install-WindowsFeature Web-FTP-Server -IncludeAllSubFeature -IncludeManagementTools
Install-WindowsFeature Web-Server -IncludeAllSubFeature -IncludeManagementTools
Import-Module WebAdministration -ea Stop
if ($rawDisk) {
$FTPRootDir = 'E:\FTP_Root'
} else {
$FTPRootDir = 'C:\FTP_Root'
}
$FTPPort = 21
$null = New-Item $FTPRootDir -ItemType Directory
New-WebFtpSite -Name $FTPSiteName -Port $FTPPort -PhysicalPath $FTPRootDir
$FTPSitePath = "IIS:\Sites\$FTPSiteName"
$BasicAuth = 'ftpServer.security.authentication.basicAuthentication.enabled'
Set-ItemProperty -Path $FTPSitePath -Name $BasicAuth -Value $True
$writeFTPuser | % {
$Param = @{
Filter = "/system.ftpServer/security/authorization"
Value = @{
accessType = "Allow"
Users = "$_"
permissions = 3
}
PSPath = 'IIS:\'
Location = $FTPSiteName
}
Add-WebConfiguration @param
}
$readFTPuser | % {
$Param = @{
Filter = "/system.ftpServer/security/authorization"
Value = @{
accessType = "Allow"
Users = "$_"
permissions = 1
}
PSPath = 'IIS:\'
Location = $FTPSiteName
}
Add-WebConfiguration @param
}
$NoneSID = ((New-Object System.Security.Principal.NTAccount("", "none")).Translate([System.Security.Principal.SecurityIdentifier])).Value
$writeFTPSDDL = ""
$writeFTPuser | % {
$SID = ((New-Object System.Security.Principal.NTAccount("", "$_")).Translate([System.Security.Principal.SecurityIdentifier])).Value
$writeFTPSDDL += "(A;OICI;0x1301bf;;;$SID)"
}
$readFTPSDDL = ""
$readFTPuser | % {
$SID = ((New-Object System.Security.Principal.NTAccount("", "$_")).Translate([System.Security.Principal.SecurityIdentifier])).Value
$readFTPSDDL += "(A;;0x1200a9;;;$SID)"
}
$sddl = "O:BAG:$NoneSID`D:PAI(A;OICI;FA;;;SY)(A;OICI;0x1301bf;;;NS)(A;OICI;FA;;;BA)$writeFTPSDDL$readFTPSDDL"
$_ClientWRITE = Join-Path $FTPRootDir "WRITE"
$null = New-Item $_ClientWRITE -ItemType Directory
$securityDescriptor = Get-Acl -Path $_ClientWRITE
$securityDescriptor.SetSecurityDescriptorSddlForm($sddl)
Set-Acl -Path $_ClientWRITE -AclObject $securityDescriptor
$writeFTPSDDL = ""
$writeFTPuser | % {
$SID = ((New-Object System.Security.Principal.NTAccount("", "$_")).Translate([System.Security.Principal.SecurityIdentifier])).Value
$writeFTPSDDL += "(A;OICIIO;0x1301bf;;;$SID)(A;;0x1200ad;;;$SID)"
}
$readFTPSDDL = ""
$readFTPuser | % {
$SID = ((New-Object System.Security.Principal.NTAccount("", "$_")).Translate([System.Security.Principal.SecurityIdentifier])).Value
$readFTPSDDL += "(A;OICI;0x1200a9;;;$SID)"
}
$sddl = "O:BAG:$NoneSID`D:PAI(A;OICI;FA;;;SY)(A;OICIIO;0x1301bf;;;NS)(A;OICI;FA;;;BA)$writeFTPSDDL$readFTPSDDL"
$_ClientREAD = Join-Path $FTPRootDir "READ"
$null = New-Item $_ClientREAD -ItemType Directory
$securityDescriptor = Get-Acl -Path $_ClientREAD
$securityDescriptor.SetSecurityDescriptorSddlForm($sddl)
Set-Acl -Path $_ClientREAD -AclObject $securityDescriptor
Set-ItemProperty -Path $FTPSitePath -Name ftpServer.security.ssl.controlChannelPolicy -Value 1
Set-ItemProperty -Path $FTPSitePath -Name ftpServer.security.ssl.dataChannelPolicy -Value 1
$newCert = New-SelfSignedCertificate -FriendlyName "FTP Server" -CertStoreLocation "Cert:\LocalMachine\My" -DnsName $dnsName -NotAfter (Get-Date).AddMonths(120)
Set-ItemProperty -Path $FTPSitePath -Name ftpServer.security.ssl.serverCertHash -Value $newCert.GetCertHashString()
$publicIP = Invoke-RestMethod http://ipinfo.io/json | Select-Object -exp ip
if (!$publicIP) { $publicIP = Read-Host "Enter !PUBLIC! IP address of this FTP server" }
Set-ItemProperty -Path $FTPSitePath -Name ftpServer.firewallSupport.externalIp4Address -Value $publicIP
cmd /c "$env:windir\System32\inetsrv\appcmd set config /section:system.ftpServer/firewallSupport /lowDataChannelPort:60000 /highDataChannelPort:65535"
Restart-WebItem "IIS:\Sites\$FTPSiteName" -Verbose
New-NetFirewallRule -Name "FTP 21" -DisplayName "FTP 21" -Description "default rule seems to not work" -Profile private, public, domain -Direction Inbound -Action Allow -Protocol TCP -LocalPort 21 -Program "%windir%\system32\svchost.exe"
netsh advfirewall set global Statefulftp disable
Restart-Service ftpsvc -Force
Write-Warning "Don't forget to:`n - set FW (Security Groups) in AWS (use existing 'FTP server')`n - set Elastic IP of this server in it's DNS record"
Write-Warning "Check NTFS permission on $FTPRootDir if it suit your needs!"