I have been struggling to get a script to work that can FTP a file to a remote windows server and wanted to see if I could get some help. I searched through multpile pages on StackOverflow and Google and have been unsuccessful so far.Below is the code I have
Logic is as follows:
Pick up the oldest file with a pattern within a folder
Connect to FTP server
Copy the file to the remote server
Move the file from the in folder to an archive folder
Code seems to be failing where I try to FTP the file to the server - $webclient.UploadFile($uri,$latestfile)
Getting this exception:
Exception calling "UploadFile" with "2" argument(s): "An exception occurred during a WebClient request."
At C:\Downloads\powershell\testmove3.ps1:22 char:26
+ $webclient.UploadFile <<<< ($uri,$latestfile)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
$source = "C:\downloads\"
$destination = "C:\dest\"
Get-ChildItem -Path C:\downloads | Where-Object { $_.name -like "TEST.CSV-PlainText*.txt" }
$latestfile=gci -path $source | Where-Object { $_.name -like "TEST.CSV-PlainText*.txt"} | sort FirstWriteTime | select -last 1
"Oldest File $latestfile"
## Get ftp object
$ftp_client = New-Object System.Net.WebClient
$user="someuser"
$pass="somepass"
$ftp_address = "ftp://ftp.testserver.com"
## Make uploads
$uri = New-Object System.Uri($ftp+$item.Name)
"Item is $latestfile"
$webclient.UploadFile($uri,$latestfile)
"File uploaded to remote servr"
Move-Item $latestfile.FullName $destination
"File $latestfile moved"
Ok, was on it overnight and happy to report that I got a solution - yeeehaw !!
I have even added a logic to trap an exception and send out an email notification. Hope this helps anyone with their FTP issues using Powershell. At the end, it returns a success or failure code to the calling program.
#PowerShell.exe -File "C:\temp\FTP.ps1"
trap [Exception] {
$recipient = "recipient#yahoo.com"
$sender = "sender#yahoo.com"
$server = "test.mailserver.com"
$subject = "FTP Test"
$body = "Exception Title: " + $_.Exception.GetType().FullName + "`r`n`r`n" + "Exception Details: " + $_.Exception.Message
$msg = new-object System.Net.Mail.MailMessage $sender, $recipient, $subject, $body
$client = new-object System.Net.Mail.SmtpClient $server
$client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$client.Send($msg)
exit 1
}
$ftpuser = "testuser"
$ftppass = "testpass"
$ftpserver = "ftp://ftp.testserver.com/"
$file = "C:\temp\one.txt"
$filenewname = "one.txt"
$webclient = New-Object System.Net.WebClient
$ftp = $ftpserver+$filenewname
$uri = New-Object System.Uri($ftp)
#Error was happening because the method call was attempting to use the HttpProxy on the Server machine.
#If the proxy is not set to null explicitly in your code, then you will get error - "An exception occurred during a webclient request"
$webclient.Proxy = $NULL
$webclient.Credentials = New-Object System.Net.NetworkCredential($ftpuser,$ftppass)
"Uploading $filenewname in $ftpserver"
$webclient.UploadFile($uri,$file)
"Uploaded $filenewname in $ftpserver"
return 0
If this is going to be used in any live support environment, I like to suggest using a prompt for password:
$ftppass = Read-Host "Enter password" -AsSecureString
Related
I am trying to understand what this error actually means. I am new to PowerShell and cannot
figure this one out. I have searched for similar questions but the content differs to my
requirement.
In a nut shell the script is queering a data historian system for a batch/lot number and the
start time of that batch.
This script will run every minute using task scheduler. This has not been set up yet as I am
still in the testing phase.
I have set up a service account is order for the script to run. The details of which are
stored in a cred file.
The script creates a folder using this batch/lot number.
The script creates a log file with the batch number and the start date and time of the batch.
Then the script searches a source folder on the server when a file is uploaded from the
factory floor into the source folder the script moves the file into the already created folder
with the correct batch number.
If files that are outside of the batch start and end time then the files are moved to no batch
folder where they will be reviewed manually.
I have done tests whereby I manually added files to the source folder on the server and
everything worked and did not get the "a positional parameter cannot be found that accepts
argument "+" from the script.
I am looking into the server configuration and permission levels but to my knowledge, nothing
has changed. I cannot see what is wrong with the script but hopefully, someone can give me
some pointers.
Error Code below
`PS C:\Users\a-graydx2> E:\Kistler Script\Batch ID with log 2021-11-29.ps1
An error occurred:
Key not valid for use in specified state.
Add-Content : A positional parameter cannot be found that accepts argument '+'.
At E:\Kistler Script\Batch ID with log 2021-11-29.ps1:186 char:11
+ Add-Content -Path $ErrorFileName -Value (Get-Date -Format " ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Add-Content], ParameterBindingException
+ FullyQualifiedErrorId :
PositionalParameterNotFound,Microsoft.PowerShell.Commands.AddContentCommand
An error occurred:
Key not valid for use in specified state.
Add-Content : A positional parameter cannot be found that accepts argument '+'.
At E:\Kistler Script\Batch ID with log 2021-11-29.ps1:186 char:11
+ Add-Content -Path $ErrorFileName -Value (Get-Date -Format " ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Add-Content], ParameterBindingException
+ FullyQualifiedErrorId :
PositionalParameterNotFound,Microsoft.PowerShell.Commands.AddContentCommand`
Script is below
Thanks for your help
`# Declare global variables
$fmSourcePath = "E:\Kistler\CoMo Services\Data\5336_L1.4345277\"
$shSourcePath = "E:\Kistler\CoMo Services\Data\5338_L1.5338_L1\"
$fmDesinationPath = "E:\Kistler XML Files\FM\"
$shDesinationPath = "E:\Kistler XML Files\SH\"
$fmWebAPI = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$shWebAPI = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# the path to stored credential
$credPath = "E:\Kistler Script\Cred.xml"
$logFileName = "BatchLog.txt"
#Path to the error log file
$ErrorFileName = "E:\Kistler Script\errorlog.txt"
function Move_Kistler_Files {
param (
[string]$url,
[string]$SourcePath,
[string]$DestinationPath
)
try {
# check for stored credential
if ( Test-Path $credPath ) {
#crendetial is stored, load it
$cred = Import-CliXml -Path $credPath
} else {
# no stored credential then: create store, get credential and save it
$parent = split-path $credpath -parent
if ( -not ( test-Path $parent ) ) {
New-Item -ItemType Directory -Force -Path $parent
}
$cred = get-credential
$cred | Export-CliXml -Path $credPath
}
# Get the current batch id using the Web-API call
$result = Invoke-RestMethod -Uri $url -Credential $Cred
$BatchID = $result.Value
$BatchFolder = $DestinationPath + $BatchID
Write-Host $BatchFolder
# Create a new folder in the destination path based on the Batch ID
If(!(test-path $BatchFolder))
{
New-Item -ItemType Directory -Force -Path $BatchFolder | Out-Null
# Add the current date/time to the log file
$LogFile = $DestinationPath + $logFileName
# if file exist Update the last record with the batch end date
If((test-path $LogFile)){
$txt = get-content $LogFile
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] + ", " + (Get-Date)
$txt | set-content $LogFile
}else{
#add a header row in the file
Add-Content -Path $LogFile -Value "BatchID, StartDate, EndDate"
}
# create a new record in the log file with current Batch Id and date as start of
batch indicator
$msg = $BatchID + ", " + (Get-Date)
Add-Content -Path $LogFile -Value $msg
}
##############################################################################
# Copy the Kistler XML files from the source to the destination
##############################################################################
# get al the Kistler XML files in the source folder
$Files = get-childitem -path $SourcePath -Recurse | Where-Object {$_.Extension -eq ".XML"}
| Sort-Object LastWriteTime -Descending
# If we have files to process do it
if ($Files.Length -gt 0) {
# read back the batch start and end dates from the log table
$LogFile = $DestinationPath + $logFileName
$txt = get-content $LogFile
# Get the latest Batch Id and it's start date
$FileMoveCount = 0
$FileNotMoveCount = 0
$ptr = 1
$batchArray =$txt[$txt.length - $ptr ].Split(",")
$MoveToPath = $DestinationPath + $batchArray[0]
$batchStartDate = $batchArray[1]
#Process each XML file
Foreach ($File in $Files ) {
$FileTime = $File.LastWriteTime
#write-host $File.FullName $File.Name $FileTime $MoveToPath $batchStartDate
#if the XML file's date-time is older than the batch start time, skip to the
previus Batch Id and start time
while ( ([DateTime]$FileTime -lt [DateTime]$batchStartDate) -and ($ptr -lt
($txt.length)-1) ) {
#Write a log for the number of files copied
if ($FileMoveCount -gt 0){
Add-Content -Path $ErrorFileName -Value ((Get-Date -Format "dd/MM/yyyy
HH:mm") + ": " + $FileMoveCount + " XML files moved to " + $MoveToPath)
$FileMoveCount = 0
}
$ptr++
$batchArray =$txt[$txt.length - $ptr ].Split(",")
$MoveToPath = $DestinationPath + $batchArray[0]
$batchStartDate = $batchArray[1]
#write-host $MoveToPath $batchStartDate
}
#Copy the XML file to the destination folder
if ([DateTime]$FileTime -ge [DateTime]$batchStartDate){
Move-Item $File.FullName -Destination ($MoveToPath + "\" + $File.Name)
$FileMoveCount++
}else{
Move-Item $File.FullName -Destination ($DestinationPath + "\NoBatch\" +
$File.Name)
$FileNotMoveCount++
}
}
#Write a log for the number of files copied
if ($FileMoveCount -gt 0){
Add-Content -Path $ErrorFileName -Value ((Get-Date -Format "dd/MM/yyyy HH:mm") + ": "
+ $FileMoveCount + " XML files moved to " + $MoveToPath)
}
if ($FileNotMoveCount -gt 0){
Add-Content -Path $ErrorFileName -Value ((Get-Date -Format "dd/MM/yyyy HH:mm") + ":
Could not find batch ID for " + $FileNotMoveCount + " XML files " )
}
}
}catch{
#Write the error
Write-Host "An error occurred:" -ForegroundColor red
Write-Host $_ -ForegroundColor red
Add-Content -Path $ErrorFileName -Value (Get-Date -Format "dd/MM/yyyy HH:mm") + ": " +
$_
}
}
### Process the FM Kistler files
Move_Kistler_Files $fmWebAPI $fmSourcePath $fmDesinationPath
### Process the SH Kistler files
Move_Kistler_Files $shWebAPI $shSourcePath $shDesinationPath`
Below is section of a large script that configures range of tasks remotely on a Active Directory server.
The script asks the user to enter OU name, saves it in a variable and passes it to AD server via Invoke-Command and $Using scope to transfer variable value to remote host and process the request
$value = Read-Host -Prompt "Enter Unique Name"
Invoke-Command -Session $testsession -ScriptBlock {
$DDN = "DC=Test,DC=net"
$OUdn = "OU=MainOU,"+$DDN
$COU = $Using:value
$Cdn = "OU="+$COU
$CPath = $Cdn+","+$OUdn
While ($true) {
Write-Host "Checking existence of OU"
if (Get-ADOrganizationalUnit -Filter "distinguishedName -eq '$CPath'") {
Write-Host "$COU OU exists."
$COU = $Null
$Cdn = $Null
$Cpath = $Null
$COU = Read-Host -Prompt "Enter Unique Name"
$Cdn="OU="+$CustOU
$CPath=$Cdn+","+OUdn
}else {
Write-Host "$COU is new"
New-ADOrganizationalUnit $COU -path $OUdn
if (Get-ADOrganizationalUnit -Filter "distinguishedName -eq '$CPath'") {
write-host " $COU is created "
}
Break
}
}
}
It gives the desired result when it is run separately and creates OU with the name provided under "Main OU". However, when it is combined with the main script it throws exception error. Main script also prompts to enter some more info which are used in other sections successfully but just not working in this section.
Am I missing anything? Your suggestions and helps are appreciated.
Object reference not set to an instance of an object.
+ CategoryInfo : NotSpecified: (:) [Get-ADOrganizationalUnit], NullReferenceException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.NullReferenceException,Microsoft.ActiveDirectory.Management.Commands.GetADOrganizationalUnit
This will help you understand why you're getting the error and will also help you in future scripts:
try
{
New-ADOrganizationalUnit $COU -path $OUdn
}
catch
{
#(
"Failed to create New Organizational Unit:"
"$_"
"Value for `$COU was: $COU"
"Value for `$OUdn was: $OUdn"
) | Out-String | Write-Warning
break
}
For more info: about_Try_Catch_Finally
As a side note, I don't see $CustOU being previously defined and I believe this a typo $Cdn+","+OUdn.
I am new to Powershell scripting. I am trying to run a script to convert an Excel spreadsheet into a PDF file. This is the script I am using:
$excelInputPath = <Path1>;
$pdfOutputPath = <Path2>;
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -As [type];
$objExcel = New-Object -ComObject excel.application;
$objExcel.visible = $False;
$workbook = $objExcel.workbooks.open($excelInputPath, 3);
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $pdfOutputPath);
$objExcel.Workbooks.close();
$objExcel.Quit()
On its own, it executes perfectly. I now wish to add a timeout and so I am trying to run it as a job. However, this job throws an execution error which I catch via the Recieve-Job command:
$excelInputPath = <Path1>;
$pdfOutputPath = <Path2>;
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -As [type];
$objExcel = New-Object -ComObject excel.application;
$objExcel.visible = $False;
$workbook = $objExcel.workbooks.open($excelInputPath, 3);
$job = Start-Job -ScriptBlock {$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $pdfOutputPath);}
$job | Wait-Job -Timeout 10;
If ($job.State -eq 'Running')
{$job.StopJob();
$objExcel.Workbooks.close();
$objExcel.Quit()
throw "Error encountered: Operation timed out"}
else
{if($job.Childjobs[0].Error)
{$objExcel.Workbooks.close();
$objExcel.Quit()
$job | Receive-Job}
else
{$job | Receive-Job;
$objExcel.Workbooks.close();
$objExcel.Quit()}
}
The output I receive is quoted below. The message is in German and roughly translates to: "It is not possible to execute a method for an expression that has a NULL".
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
57 Job57 BackgroundJob Completed True localhost $workbook.ExportAsFixe...
Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : localhost
Another confusing thing here is that the Job -State is reflected as complete even when it doesn't perform the conversion and throws this error message.
Thank you! And apart from an explanation on why this is happening, I would appreciate any input on how I can perform this task better.
The scriptblock has no knowledge of the variables $workbook, $xlFixedFormat::xlTypePDF and $pdfOutputPath.
You need to send these as parameters using the -ArgumentList parameter.
Try:
$scriptBlock = {
param($workbook, $format, $outPath)
$workbook.ExportAsFixedFormat($format, $outPath)
}
$job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $workbook, $xlFixedFormat::xlTypePDF, $pdfOutputPath
Very very much a PowerShell newbie here I wanted a script to scan devices on the network and report on Local Admins. Found one out there and made some minor modifications to meet my needs - but I have one mod I cant work out how to do. Hoping someone out there will know a simple way to do it ?
The scrip below will read in a list of device names - scan them and output a dated report for all devices that are live and on-line. If the device is not accessible I get the following error on screen but nothing in the report.
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!"
The code I am using is
$date = Get-Date -Format o | foreach {$_ -replace ":", "."}
ECHO "Starting scan"
$Result = #()
foreach($server in (gc .\servers.txt)){
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Filename = "c:\" + "LocalAdminAudit" + $date + ".txt"
function getAdmins
{
ECHO "SEARCHING FOR DEVICE"
$members = ($Group.psbase.invoke(”Members”) | %
{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) -
replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/',
'DOMAIN\') -replace ('WinNT://', '')
$members}
ECHO "READY TO WRITE OUTPUT"
$Result += Write-Output "SERVER: $server"
$Result += Write-Output ' '
$Result += ( getAdmins )
$Result += Write-Output '____________________________'
$Result += Write-Output ' '
ECHO "Record written"
}
# Added date run to report
$result += Write-Output "Date Reported: $date"
$Result > $Filename
Invoke-Item $Filename
# replace "DOMAIN" with the domain name.
ECHO "Scan Complete"
And the on screen error when a machine is off line or otherwise doesn't respond is
Exception calling "Find" with "1" argument(s): "The network path was not found.
"
At \server\users\User.Name\Powershell Scripts\Get-Local-AdminsV3.ps1:1
0 char:40
+ $Group = $computer.psbase.children.find <<<< (”Administrators”)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
I would like when it encounters an error that it writes to the report file - something along the lines of "$computor was not accessible!" - I am pretty sure there must be an easy way of doing this - but I cant work it out so any tips would be greatly appreciated
As Matt, mentioned in the comments. You can use a Try/Catch block inside your function to catch the error.
I also made some other changes. The most major is that I changed the function to contain all of the code necessary to get the local administrator group. Then the loop just calls the function once per computer with the computer name. This function is then reusable.
Secondly rather than output to a text file, I changed to outputting to a CSV as is a more structured format that can be used better later.
Also rather than relying on writing to the console host, I used Write-Progress to report the progress of the loop.
$Servers = Get-Content .\servers.txt
$ExportFileName = "c:\LocalAdminAudit$date.csv"
function Get-LocalAdministrator {
[cmdletbinding()]
Param(
$ComputerName
)
$Group = [ADSI]("WinNT://$computername/Administrators,group")
try {
$Group.Invoke("Members") | ForEach-Object {
$User = ($_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null) -split '/')[-2,-1] -join '\'
[PSCustomObject]#{
"User" = $User
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
catch {
[PSCustomObject]#{
"User" = "Failed to Report"
"Server" = $ComputerName
"Date" = Get-Date -Format o | ForEach-Object {$_ -replace ":", "."}
}
}
}
$LocalAdmins = foreach ($Server in $Servers) {
Write-Progress -Activity "Retrieving Local Administrators" -Status "Checking $Server" -PercentComplete (([array]::indexof($Servers,$Server)/($Server.count))*100)
Get-LocalAdministrator $Server
}
$LocalAdmins | Export-CSV $ExportFileName -NoTypeInformation
Invoke-Item $ExportFileName
Lastly, be careful of smart quotes especially when cutting and pasting between Outlook and word.
Hoping someone can guide me / help me.
The issue, I have 2 servers one running a Ubuntu which has a website for clients to login and download / view reports. The other is a windows server 2012 R2 which creates / stores the reports. I need to move the files from the windows to the Ubuntu server so clients can view. The data is large currently 7gb and growing at 3 gb a year.
I need a batch file to connect using ftp and then copy the folder to a local folder. However it only needs to copy those files which have modified.
I have only ever written one batch file and I cant seem to find any ftp batch scripts which only copies modifed files.
Your my last resort as I cant seem to find a coder who knows batch script (its a dieing art). I have never used powershell so would not know where to start here.
Any help or advice please let me know.
Thanks
John
You can do it with PowerShell with winscp. Exemple :
try
{
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "example.com"
UserName = "user"
Password = "mypassword"
SshHostKeyFingerprint = "ssh-rsa 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Upload files
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
$transferResult = $session.PutFiles("d:\toupload\*", "/home/user/", $False, $transferOptions)
# Throw on any error
$transferResult.Check()
# Print results
foreach ($transfer in $transferResult.Transfers)
{
Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
}
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch [Exception]
{
Write-Host ("Error: {0}" -f $_.Exception.Message)
exit 1
}
This would be a way to do it in PowerShell. It would take files that are older then 31 days and upload them.
function FTP-Upload {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Source_File,
[Parameter(Mandatory=$true)]
[string]$Target_File,
[Parameter(Mandatory=$true)]
[string]$Target_Server,
[Parameter(Mandatory=$true)]
[string]$Target_Username,
[Parameter(Mandatory=$true)]
[string]$Target_Password
)
$FTP = [System.Net.FTPWebRequest]::Create("ftp://$Target_Server/$Target_File")
$FTP = [System.Net.FTPWebRequest]$FTP
$FTP.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$FTP.Credentials = New-Object System.Net.NetworkCredential($Target_Username,$Target_Password)
$FTP.UseBinary = $true
$FTP.UsePassive = $true
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes($Source_File)
$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()
}
$Upload_Server = "server.network.tld"
$Upload_Location = "/data/"
$Upload_Username = "ftpuser"
$Upload_Password = "ftppassword"
$Files_To_Upload = Get-ChildItem E:\Path\To\Files -Recurse | Where-Object {($_.CreationTime -le (Get-Date).AddDays(-31)) -and (!$_.PSIsContainer)}
Foreach ($File in $Files_To_Upload) {
FTP-Upload -Source_File $File.FullName -Target_File ($Upload_Location + $File.Name) -Target_Server $Upload_Server -Target_Username $Upload_Username -Target_Password $Upload_Password
}