Download SharePoint 2010 Library Items using PowerShell V2 with CSOM - sharepoint-2010

my goal is: Get the items within the folders in a SharePoint 2010 library. I'm struggling trying to get some Items from a SharePoint 2010 Library using CSOM with PowerShell.
I tried three different methods that i found in the internet but still without success. Also the Microsoft's Documentation is really sh*t in this aspect, hope somebody can help me. So here we go:
1.Method A
[Microsoft.SharePoint.Client.FileInformation]$fileInfo = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($ctx, $file.ServerRelativeUrl);
[System.IO.FileStream]$writeStream = [System.IO.File]::Open("$($libraryTargetPath)\$($file.Name)", [System.IO.FileMode]::Create);
$fileInfo.Stream.CopyTo($writeStream);
$writeStream.Close();
With the A method I get this error:
Method invocation failed because [System.Net.ConnectStream] doesn't contain a method named 'CopyTo'.
+ $fileInfo.Stream.CopyTo <<<< ($writeStream);
+ CategoryInfo : InvalidOperation: (CopyTo:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
The [System.Net.ConnectStream] can't find the method CopyTo
I was lookign information about this, in the System.Net namespace and in the "Microsoft.SharePoint.Client.FileInformation" class but without success :(
2.Method B
$binary = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($ctx, $file.ServerRelativeUrl)
$Action = [System.IO.FileMode]::Create
$new = "$($libraryTargetPath)\$($file.Name)"
$stream = New-Object System.IO.FileStream $new, $Action
$writer = New-Object System.IO.BinaryWriter($stream)
$writer.write($binary)
$writer.Close()
Method B don't give me a error, but in stead of downloading the Items, it makes empty files in the destination folder. So this method isn't downloading the items, just making new files.
3.Method C
$binary = $file.OpenBinary()
$stream = New-Object System.IO.FileStream("$($libraryTargetPath)\$($file.Name)"), Create
$writer = New-Object System.IO.BinaryWriter($stream)
$writer.write($binary)
$writer.Close()
I'm not sure if method C belongs to CSOM or to the SharPoint built-in Server side client, if it's so please let me know. This is the error i'm getting:
Method invocation failed because [Microsoft.SharePoint.Client.File] doesn't contain a method named 'OpenBinary'.
At C:\Users\Administrator\Desktop\SharePointOnPremisesBackUp\SharePointOnPremisesBackUp.ps1:77 char:31
+ $binary = $file.OpenBinary <<<< ()
+ CategoryInfo : InvalidOperation: (OpenBinary:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
And here PowerShell can't find the OpenBinary() method in Microsoft.SharePoint.Client.File and there is nearly not information about this method.
Here is the complete function I'm trying to use:
function GetDocumentLibs ($ctx, $web)
{
Function IterateFoldersRecursively([Microsoft.SharePoint.Client.Folder]$folder, [Microsoft.SharePoint.Client.ClientContext]$ctx)
{
# make sure that the "Web.Context.Url" is the current web url
if ($web.Context.Url.StartsWith($SiteCollectionUrl) -eq $true)
{
$files = $folder.Files
$ctx.Load($folder.Files)
$ctx.Load($folder.Folders)
$ctx.ExecuteQuery()
foreach ($subFolder in $folder.Folders)
{
IterateFoldersRecursively $subFolder $ctx
}
# Check if folder Exist and Skip
$libraryTargetPath = "$($TargetPath)\$($folder.ServerRelativeUrl.Replace('/', '\'))"
New-Item -Path $libraryTargetPath -ItemType Directory -Force
foreach ($file in $files)
{
# Method 1
[Microsoft.SharePoint.Client.FileInformation]$fileInfo = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($ctx, $file.ServerRelativeUrl);
[System.IO.FileStream]$writeStream = [System.IO.File]::Open("$($libraryTargetPath)\$($file.Name)", [System.IO.FileMode]::Create);
$fileInfo.Stream.CopyTo($writeStream)
$writeStream.Close()
# Method 2
$binary = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($ctx, $file.ServerRelativeUrl)
$Action = [System.IO.FileMode]::Create
$new = "$($libraryTargetPath)\$($file.Name)"
$stream = New-Object System.IO.FileStream $new, $Action
$writer = New-Object System.IO.BinaryWriter($stream)
$writer.write($binary)
$writer.Close()
# Method 3
$binary = $file.OpenBinary()
$stream = New-Object System.IO.FileStream("$($libraryTargetPath)\$($file.Name)"), Create
$writer = New-Object System.IO.BinaryWriter($stream)
$writer.write($binary)
$writer.Close()
# delete folder
}
}
}
$folder = $web.GetFolderByServerRelativeUrl($web.ServerRelativeUrl)
$ctx.Load($folder)
$ctx.ExecuteQuery()
IterateFoldersRecursively $folder $ctx
}
the tools I'm using:
Sapien's PowerShell Studio
PowerShell V2 with CSOM
SharePoint 2010 OnPremises
Please if you have any Solution, Reference, Documentation or tutorial that can be useful tell me. Thanks in advance.

After a lot of research i found a solution and decide to use this method:
function GetDocumentLibs ($ctx, $web)
{
$site = $ctx.Site
$ctx.Load($site)
$ctx.ExecuteQuery()
$siteUrl = $site.Url
Function IterateFoldersRecursively([Microsoft.SharePoint.Client.Folder]$folder, [Microsoft.SharePoint.Client.ClientContext]$ctx)
{
if ($web.Context.Url.StartsWith($SiteCollectionUrl) -eq $true)
{
$files = $folder.Files
$ctx.Load($folder.Files)
$ctx.Load($folder.Folders)
$ctx.ExecuteQuery()
foreach ($subFolder in $folder.Folders)
{
IterateFoldersRecursively $subFolder $ctx
}
$targetPath = "$($TargetPath)\$($folder.ServerRelativeUrl.Replace('/', '\'))"
New-Item -Path $targetPath -ItemType Directory -Force
foreach ($file in $files)
{
$client = new-object System.Net.WebClient
$client.UseDefaultCredentials = $true
$client.DownloadFile("$($siteUrl)$($file.ServerRelativeUrl)", "$($targetPath)\$($file.Name)")
}
}
}
$folder = $web.GetFolderByServerRelativeUrl($web.ServerRelativeUrl)
$ctx.Load($folder)
$ctx.ExecuteQuery()
IterateFoldersRecursively $folder $ctx
}
Remember to implement some exception handling.
I hope this is helpful for somebody with the same problem.

Related

Data inserting to ODBC destination with powershell

I need to load data table to ODBC driver connection with powershell.
With OLEDB and SQL server we can use Bulk Copy and insert data quickly.
Is there such posibility with ODBC ?
I'm using powershell because it shoud have the best support for these kind of opperations,
but my current code doesn't utillise an of the dlls.
So my code firstly needs to create an insert statements with two for loops and iterate on every row and hold it in its memory,
and then to construct INSERT INTO with 1000 rows, and then repeat same thing.
Am i doomed to something like this ?
$Datatable = New-Object System.Data.DataTable
$tabledump= $src_cmd.ExecuteReader()
$Datatable.Load($tabledump)
foreach ($item in $Datatable.Rows) {
$f +=1
for ($i = 0; $i -lt $item.ItemArray.Length; $i++) {
$items = $item[$i] -replace "'" , "''"
$val +="'"+ $items + "',"
}
$vals += $val
if ($f % 1000 -eq 0 -or $f -eq $row_cnt) {
$values = [system.String]::Join(" ", $vals)
$values = $values.TrimEnd(",")
$cols = [system.String]::Join(",", $columns)
$postgresCommand = "Insert Into $dst_schema.$dst_table ($cols) values $values"
$dest_cmd_.CommandText = $postgresCommand
$dest_cmd_.ExecuteNonQuery()
Bad code i admit, any advice on code compositions are welcomed.
You can use Get-ODBCDSN command to retrieve the values of the ODBC connections and use it with a query
$conn.ConnectionString= "DSN=$dsn;"
$cmd = new-object System.Data.Odbc.OdbcCommand($query,$conn)
$conn.open()
$cmd.ExecuteNonQuery()
$conn.close()
https://www.andersrodland.com/working-with-odbc-connections-in-powershell/
But the ODBC provider doesnt do bulk copy
https://learn.microsoft.com/en-us/sql/relational-databases/native-client-odbc-bulk-copy-operations/performing-bulk-copy-operations-odbc?view=sql-server-ver15
I know this post is not new, but i've been fiddeling around looking for a solution and also found nothing, however this post gave me a couple of insights.
First: There is no such thing as 'Bad Code'. If it works is not bad, heck even if it didn't worked, but helped with something..
Alright, what i did is not the best solution, but i'm trying to import Active Directory data on PostgreSQL, so...
I noticed that you're trying with pgsql as well, so you can use the COPY statement.
https://www.postgresql.org/docs/9.2/sql-copy.html
https://www.postgresqltutorial.com/import-csv-file-into-posgresql-table/
In my case i used it with a csv file:
*Assuming you have installed pgsql ODBC driver
$DBConn = New-Object System.Data.Odbc.OdbcConnection
$DBConnectionString = "Driver={PostgreSQL UNICODE(x64)};Server=$ServerInstance;Port=$Port;Database=$Database;Uid=$Username;Pwd=$(ConvertFrom-SecureString -SecureString $Password);"
$DBConn.ConnectionString = $DBConnectionString
try
{
$ADFObject = #()
$ADComputers = Get-ADComputer -Filter * -SearchBase "OU=Some,OU=OrgU,OU=On,DC=Domain,DC=com" -Properties Description,DistinguishedName,Enabled,LastLogonTimestamp,modifyTimestamp,Name,ObjectGUID | Select-Object Description,DistinguishedName,Enabled,LastLogonTimestamp,modifyTimestamp,Name,ObjectGUID
foreach ($ADComputer in $ADComputers) {
switch ($ADComputer.Enabled) {
$true {
$ADEnabled = 1
}
$false {
$ADEnabled = 0
}
}
$ADFObject += [PSCustomObject] #{
ADName = $ADComputer.Name
ADInsert_Time = Get-Date
ADEnabled = $ADEnabled
ADDistinguishedName = $ADComputer.DistinguishedName
ADObjectGUID = $ADComputer.ObjectGUID
ADLastLogonTimestamp = [datetime]::FromFileTime($ADComputer.LastLogonTimestamp)
ADModifyTimestamp = $ADComputer.modifyTimestamp
ADDescription = $ADComputer.Description
}
}
$ADFObject | Export-Csv $Env:TEMP\TempPsAd.csv -Delimiter ',' -NoTypeInformation
docker cp $Env:TEMP\TempPsAd.csv postgres_docker:/media/TempPsAd.csv
$DBConn.Open()
$DBCmd = $DBConn.CreateCommand()
$DBCmd.CommandText = #"
COPY AD_Devices (ADName,ADInsert_Time,ADEnabled,ADDistinguishedName,ADObjectGUID,ADLastLogonTimestamp,ADModifyTimestamp,ADDescription)
FROM '/media/TempPsAd.csv'
DELIMITER ','
CSV HEADER
"#
$DBCmd.ExecuteReader()
$DBConn.Close()
docker exec postgres_docker rm -rf /media/TempPsAd.csv
Remove-Item $Env:TEMP\TempPsAd.csv -Force
}
catch
{
Write-Error "$($_.Exception.Message)"
continue
}
Hope it helps!
Cheers!

Powershell Salesforce SOAP API SessionHeader Type Converion Issue

Hi I am having trouble setting the SessionHeaderValue. I am basing my code on c#.Net. The login works and I receive the serviceUrl and sessionId in the login result but I can't get the session Id set in the session header
Here is the code
$uri = "c:\installs\sforce.wsdl"
$username = "username"
$password = "password"
# Proxy
$service = New-WebServiceProxy -Uri $uri -Namespace sforce -UseDefaultCredential
# Login
$loginResult = $service.login($username, $password)
$service.Url = $loginResult.serverUrl
$service.SessionHeaderValue = New-Object sforce.SessionHeader
This is the error I get which is a bit odd.
Exception setting "SessionHeaderValue": "Cannot convert the "sforce.SessionHeader" value of type "sforce.SessionHeader" to type "sforce.SessionHeader"."
I have been playing with this for a few hours now and have run out of ideas.
Any help is appreciated.
Anthony
The real problem is that you can't reuse $service. Your instance of $service is only good for the login, and that's it. I think it's due to the way New-WebServiceProxy works. Check this little script out:
$uri = 'file://C:\projects\CRM\SalesForce\Integration\enterprise.xml'
$api = new-webserviceproxy -uri $uri -NameSpace SalesForce
$api.GetType().Module.Assembly.ManifestModule.ScopeName
$api = new-object SalesForce.SforceService.ScopeName
$api.GetType().Module.Assembly.ManifestModule
If you were to run this script, (of course you'd need to substitute your own WSDL), you'd see something like this (the DLLs are dynamic, so the names will be different):
mhgl0l5w.dll
vzecopaq.dll
Notice that not only are the object references for $api different, but the dynamic assembly that New-WebServiceProxy creates is different for each one, which isn't what you might expect. That's why your cast is failing; your objects with the same name are different because they come from different dynamic assemblies. I'm not sure why the behavior is this way - if it's a peculiarity of New-WebServiceProxy, or somehow in SalesForce's WSDL.
The solution is actually pretty simple. Recreate your service object off the namespace generated by New-WebServiceProxy like so:
$uri = "c:\installs\sforce.wsdl"
$username = "username"
$password = "password"
# Proxy
$service = New-WebServiceProxy -Uri $uri -Namespace sforce -UseDefaultCredential
# Login
$loginResult = $service.login($username, $password)
$service = New-Object sforce.SforceService
$service.Url = $loginResult.serverUrl
$service.SessionHeaderValue = New-Object sforce.SessionHeader
I found a webpage that implies the the $service.SessionHeaderValue instance should be created using something like:
$service.SessionHeaderValue = New-ObjectFromProxy -proxy $service -proxyAttributeName "SessionHeaderValue" -typeName "SessionHeader"
There was a definition for the New-ObjectFromProxy function in PowerShell + SOAP + AuthenticationInfoValue:
function New-ObjectFromProxy {
param($proxy, $proxyAttributeName, $typeName)
# Locate the assembly for $proxy
$attribute = $proxy | gm | where { $_.Name -eq $proxyAttributeName }
$str = "`$assembly = [" + $attribute.TypeName + "].assembly"
invoke-expression $str
# Instantiate an AuthenticationHeaderValue object.
$type = $assembly.getTypes() | where { $_.Name -eq $typeName }
return $assembly.CreateInstance($type)
}
I don't profess to be a powershell expert, but it appears the instance of the object that New-Object creates isn't really the same type that the proxy object is expecting. Confusingly, they do have the same name.
It's also worth noting that you cannot define a web service proxy twice for the same namespace. This is required for a flow where you need to go to SOAP API to login, then pass the session id to the metadata API to make your requests.
Compare:
$LoginResponse = $sf.login($username, $password)
$newSession = $LoginResponse.sessionId
$newURL = $LoginResponse.serverUrl
$service = New-Object sforce.SforceService
$service.Url = $LoginResponse.serverUrl
$service.SessionHeaderValue = New-Object sforce.SessionHeader
$service.SessionHeaderValue.sessionId = $LoginResponse.sessionId
# Set the batch size to 2000
# Though, if it contains two long text area fields, it will set it as 200
# This is to avoid long SOAP messages and is controlled by SF
$service.QueryOptionsValue = new-Object sforce.QueryOptions
$service.QueryOptionsValue.batchSize = 2000
$service.QueryOptionsValue.batchSizeSpecified = $true
$mdservice= new-WebServiceProxy -URI $mdwsdl -Namespace sforce
$mdservice = New-Object sforce.MetadataService
$mdservice.Url = $LoginResponse.metadataServerUrl
$mdservice.SessionHeaderValue = New-Object sforce.SessionHeader
$mdservice.SessionHeaderValue.sessionId = $LoginResponse.sessionId
This will generate the following error:
"sforce.SessionHeader" to type "sforce.SessionHeader"."
At line:22 char:5
+ $mdservice.SessionHeaderValue = New-Object sforce.SessionHeader
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
The property 'sessionId' cannot be found on this object. Verify that the property exists and can be set.
At line:23 char:5
+ $mdservice.SessionHeaderValue.sessionId = $LoginResponse.sessionI ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
Whereas the below code will not error:
$sf = new-WebServiceProxy -URI $pwsdl -Namespace sforce
$LoginResponse = $sf.login($username, $password)
$newSession = $LoginResponse.sessionId
$newURL = $LoginResponse.serverUrl
$service = New-Object sforce.SforceService
$service.Url = $LoginResponse.serverUrl
$service.SessionHeaderValue = New-Object sforce.SessionHeader
$service.SessionHeaderValue.sessionId = $LoginResponse.sessionId
# Set the batch size to 2000
# Though, if it contains two long text area fields, it will set it as 200
# This is to avoid long SOAP messages and is controlled by SF
$service.QueryOptionsValue = new-Object sforce.QueryOptions
$service.QueryOptionsValue.batchSize = 2000
$service.QueryOptionsValue.batchSizeSpecified = $true
$mdservice= new-WebServiceProxy -URI $mdwsdl #Don't set namespace here
# Notice that we are getting the namespace dynamically
$type = $mdservice.GetType().NameSpace
$mdservice = New-Object ($type + '.MetadataService')
$mdservice.Url = $LoginResponse.metadataServerUrl
$mdservice.SessionHeaderValue = New-Object ($type + '.SessionHeader')
$mdservice.SessionHeaderValue.sessionId = $LoginResponse.sessionId
Exception setting "SessionHeaderValue": "Cannot convert the "sforce.SessionHeader" value of type "sforce.SessionHeader" to type "sforce.SessionHeader"."
$service.SessionHeaderValue = New-Object sforce.SessionHeader
After New-Object you have to specify the correct class name. Try this:
#Login to Salesforce
$loginResults = $service.login($username,$password)
#set the session Id in partner Object
$service.Url = $loginResults.serverUrl
$sessionHeaderObjClassName = ($service.GetType().FullName) -replace "SforceService","SessionHeader"
$service.SessionHeaderValue = New-Object $sessionHeaderObjClassName
$service.SessionHeaderValue.sessionId = $loginResults.sessionId

Upload a file to Sharepoint 2010 with powershell 2.0

I'm struggling since a couple of days to upload files to Sharepoint 2010 with powershell.
I'm on a win7 machine with powershell v2 trying to upload to a SP 2010 site.
I'm having 2 major issues
$Context.web value is always empty even after Executequery() and no
error is shown. My $Context variable gets the server version (14.x.x.x.x) but nothing more
$Context.Load($variable) which always returns the error Cannot find an overload for "Load" and the argument count: "1".
I copied Sharepoint DLLs to my Win7 machine and I import the reference to my script.
The below script is a mix of many parts I took from the net.
I'v already tried unsuccessfully to add an overload on the clientcontext defining Load method without Type parameter suggested in the following post
http://soerennielsen.wordpress.com/2013/08/25/use-csom-from-powershell/
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
$site = "https://Root-of-my-site"
$listname = "My-folder"
$context = New-Object Microsoft.SharePoint.Client.ClientContext($site)
[Microsoft.SharePoint.Client.Web]$web = $context.Web
[Microsoft.SharePoint.Client.List]$list = $web.Lists.GetByTitle($listName)
$Folder = "C:\temp\Certificates"
$List = $Context.Web.Lists.GetByTitle($listname)
Foreach ($File in (dir $Folder))
{
$FileCreationInfo = New-Object Microsoft.SharePoint.Client.FileCreationInformation
$FileCreationInfo.Overwrite = $true
$FileCreationInfo.Content = get-content -encoding byte -path $File.Fullname
$FileCreationInfo.URL = $File
$Upload = $List.RootFolder.Files.Add($FileCreationInfo)
$Context.Load($Upload)
$Context.ExecuteQuery()
}
The error is
Cannot find an overload for "Load" and the argument count: "1".
At C:\temp\uploadCertToSharepoint.ps1:48 char:14
+ $Context.Load <<<< ($Upload)
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Can someone please help me sorting this issue?
I'll need to upload around 400 files with ad-hoc fields to a sharepoint site in a couple of weeks and at the moment I'm completely stuck. Running the script server side is unfortunately not possible.
Thanks,
Marco
This error occurs since ClientRuntimeContext.Load is a Generics Method:
public void Load<T>(
T clientObject,
params Expression<Func<T, Object>>[] retrievals
)
where T : ClientObject
and Generics methods are not supported natively in PowerShell (V1, V2) AFAIK.
The workaround is to invoke a generic methods using MethodInfo.MakeGenericMethod method as described in article Invoking Generic Methods on Non-Generic Classes in PowerShell
In case of ClientRuntimeContext.Load method, the following PS function could be used:
Function Invoke-LoadMethod() {
param(
$clientObjectInstance = $(throw “Please provide an Client Object instance on which to invoke the generic method”)
)
$ctx = $clientObjectInstance.Context
$load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")
$type = $clientObjectInstance.GetType()
$clientObjectLoad = $load.MakeGenericMethod($type)
$clientObjectLoad.Invoke($ctx,#($clientObjectInstance,$null))
}
Then, in your example the line:
$Context.Load($Upload)
could be replaced with this one:
Invoke-LoadMethod -clientObjectInstance $Upload
References
Invoking Generic Methods on Non-Generic Classes in PowerShell
Some tips and tricks of using SharePoint Client Object Model in
PowerShell. Part 1
It throws the error because in powershell 2.0 you cannot call generic method directly.
You need to create closed method using MakeGenericMethod. Try to use code below.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
$site = "http://server"
$listname = "listName"
$Folder = "C:\PS\Test"
$context = New-Object Microsoft.SharePoint.Client.ClientContext($site)
[Microsoft.SharePoint.Client.Web]$web = $context.Web
[Microsoft.SharePoint.Client.List]$list = $web.Lists.GetByTitle($listName)
$method = $Context.GetType().GetMethod("Load")
$closedMethod = $method.MakeGenericMethod([Microsoft.SharePoint.Client.File])
Foreach ($File in (dir $Folder))
{
$FileCreationInfo = New-Object Microsoft.SharePoint.Client.FileCreationInformation
$FileCreationInfo.Overwrite = $true
$FileCreationInfo.Content = (get-content -encoding byte -path $File.Fullname)
$FileCreationInfo.URL = $File
$Upload = $List.RootFolder.Files.Add($FileCreationInfo)
$closedMethod.Invoke($Context, #($Upload, $null) )
$Context.ExecuteQuery()
}

PowerShell RoboCopy path issue

Objective: Robo copy from multiple machines on the network to a network share using variables for both the machine name and the currently logged on user.
What I have: txt file with a list of computernames.
Issue: I cannot get the foreach to work with the .split("\")[1] I use on the username variable to remove the domain prefix so I can use the output from that in the robocopy path
something like
robocopy "\\$computername\c$\documents and settings\$username\backup" "\\networkshare\backup\$username\backup"
gives me the error
You cannot call a method on a null-valued expression.
At C:\Scripts\Test\backup.ps1:13 char:2
Here's what I have so far. Can somebody help please?
function Get-LoggedIn {
[CmdletBinding()]
param (
[Parameter(Mandatory=$True)]
[string[]]$computername
)
foreach ($pc in $computername){
$logged_in = (gwmi win32_computersystem -COMPUTER $pc).username
$name = $logged_in.split("\")[1]
"{1}" -f $pc,$name
}
}
$computers = Get-Content "C:\Scripts\testcomputers.txt"
foreach ($computer in $computers) {
$users = Get-LoggedIn $computer
}
$SourceFolder = "\\$computer\c$\users\$users\desktop"
$DestinationFolder = "\\networkshare\backups\$users\backup\desktop"
$Logfile = "\\networkshare\backups\$users\backup\backuplog.txt"
Robocopy $SourceFolder $DestinationFolder /E /R:1 /W:1 /LOG:$Logfile
I see multiple errors here. You're not running the copy commands inside the foreach-loop. The username property recieved from WMI can often be in the following format:
domain\computer\username (or computer\domain\username, unsure since I'm on non-domain workstation now)
Anyways, the username is always the last part, so get it by using the index [-1] instead.
Updated script (with indents!):
function Get-LoggedIn {
[CmdletBinding()]
param (
[Parameter(Mandatory=$True)]
[string[]]$computername
)
foreach ($pc in $computername){
$logged_in = (gwmi win32_computersystem -COMPUTER $pc).username
$name = $logged_in.split("\")[-1]
"{1}" -f $pc,$name
}
}
$computers = Get-Content "C:\Scripts\testcomputers.txt"
foreach ($computer in $computers) {
$users = Get-LoggedIn $computer
$SourceFolder = "\\$computer\c$\users\$users\desktop"
$DestinationFolder = "\\networkshare\backups\$users\backup\desktop"
$Logfile = "\\networkshare\backups\$users\backup\backuplog.txt"
& Robocopy $SourceFolder $DestinationFolder /E /R:1 /W:1 /LOG:$Logfile
}

How to create a dynamic variable in Powershell, sucha as date/time etc

Hi i am not exactly sure if my wording is right but i need a variable which contains current date/time whenever i write data to log ; how can i do that without initializing everytime.Currently everytime i need a update i use these both statements jointly.Is there an other way of doing this?
$DateTime = get-date | select datetime
Add-Content $LogFile -Value "$DateTime.DateTime: XXXXX"
please do let me know if any questions or clarifications regarding my question.
This script make the real Dynamic variable in Powershell ( Thanks to Lee Holmes and his Windows PowerShell Cookbook The Complete Guide to Scripting Microsoft's Command Shell, 3rd Edition)
##############################################################################
##
## New-DynamicVariable
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
Creates a variable that supports scripted actions for its getter and setter
.EXAMPLE
PS > .\New-DynamicVariable GLOBAL:WindowTitle `
-Getter { $host.UI.RawUI.WindowTitle } `
-Setter { $host.UI.RawUI.WindowTitle = $args[0] }
PS > $windowTitle
Administrator: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
PS > $windowTitle = "Test"
PS > $windowTitle
Test
#>
param(
## The name for the dynamic variable
[Parameter(Mandatory = $true)]
$Name,
## The scriptblock to invoke when getting the value of the variable
[Parameter(Mandatory = $true)]
[ScriptBlock] $Getter,
## The scriptblock to invoke when setting the value of the variable
[ScriptBlock] $Setter
)
Set-StrictMode -Version 3
Add-Type #"
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Lee.Holmes
{
public class DynamicVariable : PSVariable
{
public DynamicVariable(
string name,
ScriptBlock scriptGetter,
ScriptBlock scriptSetter)
: base(name, null, ScopedItemOptions.AllScope)
{
getter = scriptGetter;
setter = scriptSetter;
}
private ScriptBlock getter;
private ScriptBlock setter;
public override object Value
{
get
{
if(getter != null)
{
Collection<PSObject> results = getter.Invoke();
if(results.Count == 1)
{
return results[0];
}
else
{
PSObject[] returnResults =
new PSObject[results.Count];
results.CopyTo(returnResults, 0);
return returnResults;
}
}
else { return null; }
}
set
{
if(setter != null) { setter.Invoke(value); }
}
}
}
}
"#
## If we've already defined the variable, remove it.
if(Test-Path variable:\$name)
{
Remove-Item variable:\$name -Force
}
## Set the new variable, along with its getter and setter.
$executioncontext.SessionState.PSVariable.Set(
(New-Object Lee.Holmes.DynamicVariable $name,$getter,$setter))
There's a Set-StrictMode -Version 3 but you can set it as -Version 2 if you can load framework 4.0 in your powershell V2.0 session as explained Here
The use for the OP is:
New-DynamicVariable -Name GLOBAL:now -Getter { (get-date).datetime }
Here the Lee Holmes's evaluation (where it is clear what is the real flaw) about the method I used in my other answer:
Note
There are innovative solutions on the Internet that use PowerShell's debugging facilities to create a breakpoint that changes a variable's value whenever you attempt to read from it. While unique, this solution causes PowerShell to think that any scripts that rely on the variable are in debugging mode. This, unfortunately, prevents PowerShell from enabling some important performance optimizations in those scripts.
Why not use:
Add-Content $LogFile -Value "$((Get-Date).DateTime): XXXXX"
This gets the current datetime every time. Notice that it's inside $( ) which makes powershell run the expression(get the datetime) before inserting it into the string.
wrap your two commands in function so you will have just one call ?
function add-log{
(param $txt)
$DateTime = get-date | select -expand datetime
Add-Content $LogFile -Value "$DateTime: $txt"
}
Besides these other ways (which frankly I would probably use instead - except the breakpoint approach), you can create a custom object with a ScriptProperty that you can provide the implementation for:
$obj = new-object pscustomobject
$obj | Add-Member ScriptProperty Now -Value { Get-Date }
$obj.now
Using PsBreakPoint:
$act= #'
$global:now = (get-date).datetime
'#
$global:sb = [scriptblock]::Create($act)
$now = Set-PSBreakpoint -Variable now -Mode Read -Action $global:sb
calling $now returns current updated datetime value
One liner:
$now = Set-PSBreakpoint -Variable now -Mode Read -Action { $global:now = (get-date).datetime }