I currently have a powershell script to loop through a list on an Sharepoint online site.
$creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials("[USERNAME]", (ConvertTo-SecureString "[PASSWORD]" -AsPlainText -Force))
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext("https://[SITENAME].sharepoint.com/sites/Projekter")
$ctx.credentials = $creds
try{
$lists = $ctx.web.Lists
$list = $lists.GetByTitle("Ekstern synkronisering")
$listItems = $list.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
$ctx.load($listItems)
$ctx.executeQuery()
foreach($listItem in $listItems)
{
$internal = $listItem["ConnectionString1"]
$external = $listItem["ConnectionString2"]
$folder = $listItem["Mappe"]
$project = $listItem["Project"]
$caseNo = $listItem["Title"]
$doc = New-Object System.Xml.XmlDocument
$doc.Load("C:\Script\PS-Layer2\xmlTemplate.xml")
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$ns.AddNamespace("ns", $doc.DocumentElement.NamespaceURI)
$doc.SelectSingleNode("//ns:dataEntity[#name='Intern site']", $ns).connectionString = $internal+";Authentication=Office365;User Id=$userId;Password=$pwd;"
$doc.SelectSingleNode("//ns:dataEntity[#name='Externt site']", $ns).connectionString = $external+";Authentication=Office365;User Id=$userId;Password=$pwd;"
$doc.Save("C:\Temp\$caseNo - $project - $folder.xml")
}
}
catch{
write-host "$($_.Exception.Message)" -foregroundcolor red
}
This works fine to get the items and modify an XML file..
What I would like to do is, when the XML file has been created, it shall delete the listitem from the sharepoint site, so that it will not be created on the next run again.
Does anyone have a solution on that?
Best regards...
Related
I'm trying to build a little app to help admins swap powerapps ownership around in PowerShell. I'm sure this is me misunderstanding how scopes work in PowerShell but I'm stumped and need a little help.
The app is pretty simple, it queries the PowerApp environment for a list of apps, their owners, and their GUIDs and presents them in a datagridview. Users select the app they're going to change, click a button, put an email address in, and then click another button. On that click, the app grabs the user's GUID from AAD and then runs a command to flip ownership of the app to that user's GUID.
But for some reason, the second function keeps reporting that the GUID and App Name I collected in the first screen are empty strings.
Here's the whole thing (minus credential info, natch):
#Get Apps on environment
$apps = Get-AdminPowerApp -EnvironmentName $powerAppEnv
#Form Details
$ChangePowerAppOwnership = New-Object system.Windows.Forms.Form
$ChangePowerAppOwnership.ClientSize = New-Object System.Drawing.Point(500,300)
$ChangePowerAppOwnership.text = "Change PowerApp Ownership"
$ChangePowerAppOwnership.TopMost = $false
$appsLabel = New-Object system.Windows.Forms.Label
$appsLabel.text = "Available Apps"
$appsLabel.AutoSize = $true
$appsLabel.width = 25
$appsLabel.height = 10
$appsLabel.location = New-Object System.Drawing.Point(15,20)
$appsLabel.Font = New-Object System.Drawing.Font('Segoe UI',10)
$availableApps = New-Object system.Windows.Forms.DataGridView
$availableApps.width = 470
$availableApps.height = 200
$availableApps.location = New-Object System.Drawing.Point(15,40)
$availableApps.MultiSelect = $false
$availableApps.SelectionMode = "FullRowSelect"
$availableApps.ColumnCount = 3
$availableApps.ColumnHeadersVisible = $true
$availableApps.Columns[0].Name = "App Name"
$availableApps.Columns[1].Name = "Current Owner"
$availableApps.Columns[2].Name = "GUID"
foreach($app in $apps){
$availableApps.Rows.Add(#($app.DisplayName,($app.Owner | Select-Object -Expand displayName),$app.AppName))
}
$promptForAdmin = New-Object system.Windows.Forms.Button
$promptForAdmin.text = "Next"
$promptForAdmin.width = 60
$promptForAdmin.height = 30
$promptForAdmin.location = New-Object System.Drawing.Point(424,260)
$promptForAdmin.Font = New-Object System.Drawing.Font('Segoe UI',10)
$promptForAdmin.Add_Click({ GetNewAdmin $availableApps.SelectedRows})
$adminLabel = New-Object system.Windows.Forms.Label
$adminLabel.text = "New Administrator"
$adminLabel.AutoSize = $true
$adminLabel.width = 25
$adminLabel.height = 10
$adminLabel.location = New-Object System.Drawing.Point(14,13)
$adminLabel.Font = New-Object System.Drawing.Font('Segoe UI',10)
$adminEmailField = New-Object system.Windows.Forms.TextBox
$adminEmailField.multiline = $false
$adminEmailField.width = 200
$adminEmailField.height = 20
$adminEmailField.location = New-Object System.Drawing.Point(135,12)
$adminEmailField.Font = New-Object System.Drawing.Font('Segoe UI',10)
$changeAppAdmin = New-Object system.Windows.Forms.Button
$changeAppAdmin.text = "Go"
$changeAppAdmin.width = 60
$changeAppAdmin.height = 30
$changeAppAdmin.location = New-Object System.Drawing.Point(424,260)
$changeAppAdmin.Font = New-Object System.Drawing.Font('Segoe UI',10)
$ChangePowerAppOwnership.controls.AddRange(#($appsLabel,$availableApps,$promptForAdmin))
$ChangePowerAppOwnership.ShowDialog()
function GetNewAdmin {
param($selectedRows)
$selectedAppGuid = $selectedRows | ForEach-Object{ $_.Cells[2].Value }
$selectedAppName = $selectedRows | ForEach-Object{ $_.Cells[0].Value }
Write-Host "Selected App GUID: $selectedAppGuid" #this and the following command show values
Write-Host "Selected App Name: $selectedAppName"
$appsLabel.Visible = $false
$availableApps.Visible = $false
$promptForAdmin.Visible = $false
$changeAppAdmin.Add_Click( { AssignNewAdmin $selectedAppGuid $selectedAppName $adminEmailField.Text} )
$ChangePowerAppOwnership.controls.AddRange(#($adminLabel,$adminEmailField,$changeAppAdmin))
}
function AssignNewAdmin {
param(
$selectedAppGuid,
$selectedAppName,
$newAdminEmail
)
Write-Host "AppID: $selectedAppGuid" #this is always empty
Connect-AzureAD -Credential $credentials
$user = Get-AzureADUser -ObjectId $newAdminEmail
$newAppOwnerGuid = $user | select ObjectId
$newAppOwnerName = $user | select DisplayName
$msgBoxMessage = "Are you sure you want to grant ownership of $selectedAppName to $newAppOwnerName`?"
$msgBoxInput = [System.Windows.Forms.MessageBox]::Show($msgBoxMessage,"Confirm","YesNo","Error")
switch ($msgBoxInput){
'Yes'{
Set-AdminPowerAppOwner -AppName $selectedAppGuid -EnvironmentName $powerAppEnv -AppOwner $newAppOwnerGuid
# try{
# $ChangePowerAppOwnership.Close()
# }
# catch{
# Write-Host "Could not update this app's administrator role."
# }
}
'No' {
$ChangePowerAppOwnership.Close()
}
}
}
Move the functions to the top or at least higher than $ChangePowerAppOwnership.ShowDialog() or the script wont find them(the execution stops till you close the Form...).
The same goes for the function AssignNewAdmin as it is used in GetNewAdmin but defined later.
Courtesy of Jeroen Mostert's comment, adding GetNewClosure to my second Add_Click function did the trick.
I am trying to grab emails from Exchange using powershell in UI Path. When trying to return the items, I get the following error:
Throw : Unable to cast object of type 'Microsoft.Exchange.WebServices.Data.GetItemResponse' to type 'System.String'.
Even when I change the TypeArgument in UI Path. Currently I am using the Invoke power shell activity but I get the same issues using the RunPowershellScript activity. Below is a screenshot of my sequence in UI Path, my parameters, and my powershell script, Thank you!
Param(
[parameter()]
[string]$mailbox,
[parameter()]
[string]$password
)
try{
#https://forum.uipath.com/t/get-argument-from-an-process-with-exception-in-reframework/22537/4
function test-password(){
$Creds = new-object System.Net.networkCredential -ArgumentList $mailbox, $password
$mailFolder = "Inbox"
$itemView = 3
#[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")
$ExSer = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$ExSer.Credentials = ($Creds)
$ExSer.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")
$ExSer.AutodiscoverUrl($mailbox, {$true})
$setMailFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ExSer,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$logs2 += "Successfully Connected to mailbox $mailbox"
$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView($itemView)
$CustomFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$ifIsRead = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$false)
$ifFrom = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From,"filteremail#mycompany.com")
$CustomFilter.add($ifIsRead)
$CustomFilter.add($ifFrom)
$filteredEmails = $ExSer.FindItems($setMailFolder.Id,$CustomFilter, $iv)
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
#add-type "Microsoft.Exchange.WebServices.Data.GetItemResponse"
$ExSer.LoadPropertiesForItems($filteredEmails,$psPropertySet)
$item = $filteredEmails.Items[0]
return $mailbox, $item
}
test-password
}
catch{
Throw
}
I commented out the load dll as it seemed to work without it and was throwing a similar error when it hit it. The code seems to throw an error when it hits $Exser.LoadPropertyItems. I have also tried switching to Exchange 2007 etc. To clarify, when running purely powershell outside of UI Path, this code works just fine.
I figured it out....I was just being a dingus. It was trying to load an object, which was breaking it. If I saved it as a variable and then converted it within my script and returned what I needed as a string....guess what? It could understand the string. Below is my updated powershell.
Param(
[parameter()]
[string]$mailbox,
[parameter()]
[string]$password
)
try{
#https://forum.uipath.com/t/get-argument-from-an-process-with-exception-in-reframework/22537/4
function test-password(){
$Creds = new-object System.Net.networkCredential -ArgumentList $mailbox, $password
$mailFolder = "Inbox"
$itemView = 50
# Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
#[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")
$ExSer = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$ExSer.Credentials = ($Creds)
$ExSer.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")
$ExSer.AutodiscoverUrl($mailbox, {$true})
$setMailFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ExSer,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$logs2 += "Successfully Connected to mailbox $mailbox"
$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView($itemView)
$CustomFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$ifIsRead = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$true)
$ifFrom = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From,"emailtofilterby#mycompany.com")
$CustomFilter.add($ifIsRead)
$CustomFilter.add($ifFrom)
$filteredEmails = $ExSer.FindItems($setMailFolder.Id,$CustomFilter, $iv)
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
#add-type "Microsoft.Exchange.WebServices.Data.GetItemResponse"
$FilteredEmailitems = $ExSer.LoadPropertiesForItems($filteredEmails,$psPropertySet)
$EmailBody = $FilteredEmailitems.Item(0) | Select-Object -ExpandProperty Item | Select-Object -ExpandProperty Body | Select-Object -ExpandProperty Text
return $EmailBody
}
test-password
}
catch{
Throw
}
I have been trying to take a manual step out of a process where I clone a wiki page in sharepoint (done via powershell) and then manually go to the aspx in sharepoint designer and update the textpart's default value:
EX:
<WpNs0:SPSlicerTextWebPart runat="server" MaximumCharacters="255" DefaultValue="REPLACETHISVALUE" RequireSelection="False" FilterMainControlWidthPixels="0" FilterName="Text Filter" Title="Text Filter" FrameType="BorderOnly" SuppressWebPartChrome="False" Description="Filters the contents of Web Parts by allowing users to enter a text value." ...
I'm using the following function for building the context for my copy, and I'm wondering if there is a way to pass the final ASPX site URL and manipulate the content:
function Get-SharepointContext
{
Param(
[Parameter(Mandatory=$true)]
$siteUrl,
[Parameter(Mandatory=$false)]
$cred)
If(!$cred){$cred = get-credential -UserName "$ENV:Username#$env:USERDNSDOMAIN" -Message "Login"}
[string]$username = $cred.UserName
$securePassword = $cred.Password
[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$ctx.RequestTimeOut = 1000 * 60 * 10;
$ctx.AuthenticationMode =[Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$ctx.Credentials = $credentials
$ctx.Load($ctx.Web)
$ctx.Load($ctx.Site)
$ctx.ExecuteQuery()
Return $ctx
}
Has anyone attempted this before or know of how I can actually do this?
We can change the web part properties using PnP PowerShell or CSOM code.
PnP:
#Get Current Context Site (Root)
$siteurl = "https://abc.sharepoint.com"
Connect-SPOnline -Url $siteurl
$ctx = Get-SPOContext
#Get Web Part ID
webpart = Get-SPOWebPart -ServerRelativePageUrl "/Pages/PnPPage.aspx" -Identity "Text Filter"
$webpartId = $webpart.Id
# Update WebPart
Set-SPOWebPartProperty -ServerRelativePageUrl "/Pages/PnPPage.aspx" -Identity $webpartId -Key Height -Value 500
CSOM:
function Change-WebPart {
#variables that needs to be set before starting the script
$siteURL = "https://spfire.sharepoint.com"
$userName = "mpadmin#spfire.onmicrosoft.com"
$webURL = "https://spfire.sharepoint.com"
$relativePageUrl = "/SitePages/Home.aspx"
# Let the user fill in their password in the PowerShell window
$password = Read-Host "Please enter the password for $($userName)" -AsSecureString
# set SharePoint Online credentials
$SPOCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userName, $password)
# Creating client context object
$context = New-Object Microsoft.SharePoint.Client.ClientContext($webURL)
$context.credentials = $SPOCredentials
#get Page file
$page = $context.web.getFileByServerRelativeUrl($relativePageUrl)
$context.load($page)
#send the request containing all operations to the server
try{
$context.executeQuery()
}
catch{
write-host "Error: $($_.Exception.Message)" -foregroundcolor red
}
#use the WebPartManger to load the webparts on a certain page
$webPartManager = $page.GetLimitedWebPartManager([System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
$context.load($webPartManager.webparts)
#send the request containing all operations to the server
try{
$context.executeQuery()
}
catch{
write-host "Error: $($_.Exception.Message)" -foregroundcolor red
}
#loop through all WebParts to get the correct one and change its property
foreach($webPartDefinition in $webpartmanager.webparts){
$context.Load($webPartDefinition.WebPart.Properties)
#send the request containing all operations to the server
try{
$context.executeQuery()
}
catch{
write-host "Error: $($_.Exception.Message)" -foregroundcolor red
}
#Only change the webpart with a certain title
if ($webPartDefinition.WebPart.Properties.FieldValues.Title -eq "Documents")
{
$webPartDefinition.webpart.properties["Title"] = "My Documents"
$webPartDefinition.SaveWebPartChanges()
}
}
}
Change-WebPart
Reference:
Update/Delete WebParts On SharePoint Pages Using PnP PowerShell
Editing Web Part properties with PowerShell CSOM in SharePoint
I need to be able to script folder creation from a csv into a SharePoint Online document library with each folder with permission inheritance disabled and for different user to each folder to be added.
The following code can create the folders and disable the inheritance but it seems to try add a group but not a user. How to make it add a user instead?
Thanks.
### Get the user credentials
$credential = Get-Credential
$username = $credential.UserName
$password = $credential.GetNetworkCredential().Password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
### Input Parameters
$url = 'URL HERE'
$csvfilepath='C:\Scripts\data.csv'
$libname ='BUS61'
### References
# Specified the paths where the dll's are located.
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.dll'
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.Runtime.dll'
### CreateFolder with Permissions Function
function CreateFolderWithPermissions()
{
# Connect to SharePoint Online and get ClientContext object.
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$clientContext.Credentials = $credentials
Function GetRole
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true, Position = 1)]
[Microsoft.SharePoint.Client.RoleType]$rType
)
$web = $clientContext.Web
if ($web -ne $null)
{
$roleDefs = $web.RoleDefinitions
$clientContext.Load($roleDefs)
$clientContext.ExecuteQuery()
$roleDef = $roleDefs | Where-Object { $_.RoleTypeKind -eq $rType }
return $roleDef
}
return $null
}
# Get the SharePoint web
$web = $clientContext.Web;
$clientContext.Load($web)
#Get the groups
$groups = $web.SiteGroups
$clientContext.Load($groups)
$clientContext.ExecuteQuery()
#Read CSV File and iterate
$csv = Import-CSV $csvfilepath
foreach ($row in $csv)
{
#Create Folder
$folder = $web.Folders.Add($libname + "/" + $row.Folder)
$clientContext.Load($folder)
$clientContext.ExecuteQuery()
#Assign Role
$group = $groups.GetByName($row.Group)
$clientContext.Load($group)
$clientContext.ExecuteQuery()
$roleType= $row.Role
$roleTypeObject = [Microsoft.SharePoint.Client.RoleType]$roleType
$roleObj = GetRole $roleTypeObject
$usrRDBC = $null
$usrRDBC = New-Object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($clientContext)
$usrRDBC.Add($roleObj)
# Remove inherited permissions
$folder.ListItemAllFields.BreakRoleInheritance($false, $true)
$clientContext.Load($folder.ListItemAllFields.RoleAssignments.Add($group, $usrRDBC))
$folder.Update()
$clientContext.ExecuteQuery()
# Display the folder name and permission
Write-Host -ForegroundColor Blue 'Folder Name: ' $folder.Name ' Group: '$row.Group ' Role: ' $roleType;
}
}
#Execute the function
CreateFolderWithPermissions
Let's assume that you will define user login in your CSv file. Than you have to change the line:
$group = $groups.GetByName($row.Group)
to
$user = $web.EnsureUser($row.User)
and replace all references to $group variable with $user
More generic approach for searching for a user (with for example display name) would be using Utility.ResolvePrincipal method:
[Microsoft.SharePoint.Client.Utilities.Utility]::ResolvePrincipal($clientContext, $web, "DisplayName", ([Microsoft.SharePoint.Client.Utilities.PrincipalType]::User), ([Microsoft.SharePoint.Client.Utilities.PrincipalSource]::All), $null, $false)
I want to use PowerShell to transfer files with FTP to an anonymous FTP server. I would not use any extra packages. How?
I am not sure you can 100% bullet proof the script from not hanging or crashing, as there are things outside your control (what if the server loses power mid-upload?) - but this should provide a solid foundation for getting you started:
# create the FtpWebRequest and configure it
$ftp = [System.Net.FtpWebRequest]::Create("ftp://localhost/me.png")
$ftp = [System.Net.FtpWebRequest]$ftp
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = new-object System.Net.NetworkCredential("anonymous","anonymous#localhost")
$ftp.UseBinary = $true
$ftp.UsePassive = $true
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes("C:\me.png")
$ftp.ContentLength = $content.Length
# get the request stream, and write the bytes into it
$rs = $ftp.GetRequestStream()
$rs.Write($content, 0, $content.Length)
# be sure to clean up after ourselves
$rs.Close()
$rs.Dispose()
There are some other ways too. I have used the following script:
$File = "D:\Dev\somefilename.zip";
$ftp = "ftp://username:password#example.com/pub/incoming/somefilename.zip";
Write-Host -Object "ftp url: $ftp";
$webclient = New-Object -TypeName System.Net.WebClient;
$uri = New-Object -TypeName System.Uri -ArgumentList $ftp;
Write-Host -Object "Uploading $File...";
$webclient.UploadFile($uri, $File);
And you could run a script against the windows FTP command line utility using the following command
ftp -s:script.txt
(Check out this article)
The following question on SO also answers this: How to script FTP upload and download?
I'm not gonna claim that this is more elegant than the highest-voted solution...but this is cool (well, at least in my mind LOL) in its own way:
$server = "ftp.lolcats.com"
$filelist = "file1.txt file2.txt"
"open $server
user $user $password
binary
cd $dir
" +
($filelist.split(' ') | %{ "put ""$_""`n" }) | ftp -i -in
As you can see, it uses that dinky built-in windows FTP client. Much shorter and straightforward, too. Yes, I've actually used this and it works!
Easiest way
The most trivial way to upload a binary file to an FTP server using PowerShell is using WebClient.UploadFile:
$client = New-Object System.Net.WebClient
$client.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$client.UploadFile(
"ftp://ftp.example.com/remote/path/file.zip", "C:\local\path\file.zip")
Advanced options
If you need a greater control, that WebClient does not offer (like TLS/SSL encryption, etc), use FtpWebRequest. Easy way is to just copy a FileStream to FTP stream using Stream.CopyTo:
$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$fileStream.CopyTo($ftpStream)
$ftpStream.Dispose()
$fileStream.Dispose()
Progress monitoring
If you need to monitor an upload progress, you have to copy the contents by chunks yourself:
$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$buffer = New-Object Byte[] 10240
while (($read = $fileStream.Read($buffer, 0, $buffer.Length)) -gt 0)
{
$ftpStream.Write($buffer, 0, $read)
$pct = ($fileStream.Position / $fileStream.Length)
Write-Progress `
-Activity "Uploading" -Status ("{0:P0} complete:" -f $pct) `
-PercentComplete ($pct * 100)
}
$ftpStream.Dispose()
$fileStream.Dispose()
Uploading folder
If you want to upload all files from a folder, see
PowerShell Script to upload an entire folder to FTP
I recently wrote for powershell several functions for communicating with FTP, see https://github.com/AstralisSomnium/PowerShell-No-Library-Just-Functions/blob/master/FTPModule.ps1. The second function below, you can send a whole local folder to FTP. In the module are even functions for removing / adding / reading folders and files recursively.
#Add-FtpFile -ftpFilePath "ftp://myHost.com/folder/somewhere/uploaded.txt" -localFile "C:\temp\file.txt" -userName "User" -password "pw"
function Add-FtpFile($ftpFilePath, $localFile, $username, $password) {
$ftprequest = New-FtpRequest -sourceUri $ftpFilePath -method ([System.Net.WebRequestMethods+Ftp]::UploadFile) -username $username -password $password
Write-Host "$($ftpRequest.Method) for '$($ftpRequest.RequestUri)' complete'"
$content = $content = [System.IO.File]::ReadAllBytes($localFile)
$ftprequest.ContentLength = $content.Length
$requestStream = $ftprequest.GetRequestStream()
$requestStream.Write($content, 0, $content.Length)
$requestStream.Close()
$requestStream.Dispose()
}
#Add-FtpFolderWithFiles -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/somewhere/" -userName "User" -password "pw"
function Add-FtpFolderWithFiles($sourceFolder, $destinationFolder, $userName, $password) {
Add-FtpDirectory $destinationFolder $userName $password
$files = Get-ChildItem $sourceFolder -File
foreach($file in $files) {
$uploadUrl ="$destinationFolder/$($file.Name)"
Add-FtpFile -ftpFilePath $uploadUrl -localFile $file.FullName -username $userName -password $password
}
}
#Add-FtpFolderWithFilesRecursive -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/" -userName "User" -password "pw"
function Add-FtpFolderWithFilesRecursive($sourceFolder, $destinationFolder, $userName, $password) {
Add-FtpFolderWithFiles -sourceFolder $sourceFolder -destinationFolder $destinationFolder -userName $userName -password $password
$subDirectories = Get-ChildItem $sourceFolder -Directory
$fromUri = new-object System.Uri($sourceFolder)
foreach($subDirectory in $subDirectories) {
$toUri = new-object System.Uri($subDirectory.FullName)
$relativeUrl = $fromUri.MakeRelativeUri($toUri)
$relativePath = [System.Uri]::UnescapeDataString($relativeUrl.ToString())
$lastFolder = $relativePath.Substring($relativePath.LastIndexOf("/")+1)
Add-FtpFolderWithFilesRecursive -sourceFolder $subDirectory.FullName -destinationFolder "$destinationFolder/$lastFolder" -userName $userName -password $password
}
}
Here's my super cool version BECAUSE IT HAS A PROGRESS BAR :-)
Which is a completely useless feature, I know, but it still looks cool \m/ \m/
$webclient = New-Object System.Net.WebClient
Register-ObjectEvent -InputObject $webclient -EventName "UploadProgressChanged" -Action { Write-Progress -Activity "Upload progress..." -Status "Uploading" -PercentComplete $EventArgs.ProgressPercentage } > $null
$File = "filename.zip"
$ftp = "ftp://user:password#server/filename.zip"
$uri = New-Object System.Uri($ftp)
try{
$webclient.UploadFileAsync($uri, $File)
}
catch [Net.WebException]
{
Write-Host $_.Exception.ToString() -foregroundcolor red
}
while ($webclient.IsBusy) { continue }
PS. Helps a lot, when I'm wondering "did it stop working, or is it just my slow ASDL connection?"
You can simply handle file uploads through PowerShell, like this.
Complete project is available on Github here https://github.com/edouardkombo/PowerShellFtp
#Directory where to find pictures to upload
$Dir= 'c:\fff\medias\'
#Directory where to save uploaded pictures
$saveDir = 'c:\fff\save\'
#ftp server params
$ftp = 'ftp://10.0.1.11:21/'
$user = 'user'
$pass = 'pass'
#Connect to ftp webclient
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
#Initialize var for infinite loop
$i=0
#Infinite loop
while($i -eq 0){
#Pause 1 seconde before continue
Start-Sleep -sec 1
#Search for pictures in directory
foreach($item in (dir $Dir "*.jpg"))
{
#Set default network status to 1
$onNetwork = "1"
#Get picture creation dateTime...
$pictureDateTime = (Get-ChildItem $item.fullName).CreationTime
#Convert dateTime to timeStamp
$pictureTimeStamp = (Get-Date $pictureDateTime).ToFileTime()
#Get actual timeStamp
$timeStamp = (Get-Date).ToFileTime()
#Get picture lifeTime
$pictureLifeTime = $timeStamp - $pictureTimeStamp
#We only treat pictures that are fully written on the disk
#So, we put a 2 second delay to ensure even big pictures have been fully wirtten in the disk
if($pictureLifeTime -gt "2") {
#If upload fails, we set network status at 0
try{
$uri = New-Object System.Uri($ftp+$item.Name)
$webclient.UploadFile($uri, $item.FullName)
} catch [Exception] {
$onNetwork = "0"
write-host $_.Exception.Message;
}
#If upload succeeded, we do further actions
if($onNetwork -eq "1"){
"Copying $item..."
Copy-Item -path $item.fullName -destination $saveDir$item
"Deleting $item..."
Remove-Item $item.fullName
}
}
}
}
You can use this function :
function SendByFTP {
param (
$userFTP = "anonymous",
$passFTP = "anonymous",
[Parameter(Mandatory=$True)]$serverFTP,
[Parameter(Mandatory=$True)]$localFile,
[Parameter(Mandatory=$True)]$remotePath
)
if(Test-Path $localFile){
$remoteFile = $localFile.Split("\")[-1]
$remotePath = Join-Path -Path $remotePath -ChildPath $remoteFile
$ftpAddr = "ftp://${userFTP}:${passFTP}#${serverFTP}/$remotePath"
$browser = New-Object System.Net.WebClient
$url = New-Object System.Uri($ftpAddr)
$browser.UploadFile($url, $localFile)
}
else{
Return "Unable to find $localFile"
}
}
This function send specified file by FTP.
You must call the function with these parameters :
userFTP = "anonymous" by default or your username
passFTP = "anonymous" by default or your password
serverFTP = IP address of the FTP server
localFile = File to send
remotePath = the path on the FTP server
For example :
SendByFTP -userFTP "USERNAME" -passFTP "PASSWORD" -serverFTP "MYSERVER" -localFile "toto.zip" -remotePath "path/on/the/FTP/"
Goyuix's solution works great, but as presented it gives me this error: "The requested FTP command is not supported when using HTTP proxy."
Adding this line after $ftp.UsePassive = $true fixed the problem for me:
$ftp.Proxy = $null;
Simple solution if you can install curl.
curl.exe -p --insecure "ftp://<ftp_server>" --user "user:password" -T "local_file_full_path"