I'm having a problem with a powershell script that I wrote to call a method on a WCF web service. The WCF service has a composite DataContract as its sole request parameter. We have recently modified the ServiceContract (by adding two new methods), but the new methods are not being called by the powershell script. Here is the original contract:
[ServiceContract]
public interface IFileSystemService {
[OperationContract]
HashFileResponse HashFile(HashFileRequest req);
[OperationContract]
void GenerateFiles(GenerateFilesRequest req);
}
And here is the new contract:
[ServiceContract]
public interface IFileSystemService {
[OperationContract]
HashFileResponse HashFile(HashFileRequest req);
[OperationContract]
void GenerateFiles(GenerateFilesRequest req);
[OperationContract]
ParseFilePathResponse ParseFilePath(ParseFilePathRequest req);
[OperationContract]
ArchiveParsedFileResponse ArchiveParsedFile(ArchiveParsedFileRequest req);
}
The GenerateFiles method is the one being called by the PowerShell script. We have not modified the GenerateFilesRequest DataContract at all, which is defined below:
[DataContract]
public class GenerateFilesRequest : BaseRequest {
[DataMember(IsRequired = true)]
public int Id { get; set; }
}
Baserequest is currently an empty class for future use (all our request data contracts use it):
[DataContract]
public abstract class BaseRequest {
}
Calling this method works consistently via other means; SoapUI, Fiddler and via WCF contracts defined throughout the application.
After adding the two new methods, our integration tests are failing due to the fact the powershell script fails to call the GenerateFiles method consistently within a loop (see error output).
When I initially wrote this script, I was having a similar issue running it (though it either consistently broke, or consistently worked) but I managed to get the service calls working by adding the -Namespace and -Class arguments to the New-WebServiceProxy cmdlet.
Context: We are running a powershell script on a developer's machine, connecting to a WCF service hosted in IISExpress. All developers experience the same issues.
Here's the original script (preferred) before my recent modifications (this was working fine, but now most calls fail):
$sqlServer=$args[0]
function CallFSS($Id) {
$uri = "http://localhost:1234/FileSystemService.svc?wsdl"
$srv = New-WebServiceProxy -Uri $uri -Namespace fssNS -Class fssClass
$req = [fssNS.GenerateFilesRequest](New-Object fssNS.GenerateFilesRequest)
$req.Id= $Id
$response = $srv.GenerateFiles($req)
}
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=" + $sqlServer + ";Database=MyDatabase;Integrated Security=True"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "select Id FROM MyTable WHERE Status = 2"
$SqlCmd.Connection = $SqlConnection
$sqlResult = $SqlCmd.ExecuteReader()
while ($sqlResult.Read()) {
$Id = $sqlResult.GetInt32(0)
Write-Host Generating files for Id $Id
CallFSS $Id
}
$SqlCmd.Dispose()
$SqlConnection.Dispose()
$SqlConnection.Close()
Here is an excerpt of the output from the script. As you can see, this is vastly inconsistent (Lines marked like this succeed):
Generating files for Id 1
Cannot convert argument "req", with value: "fssNS.GenerateFilesRequest", for "GenerateFiles" to type "fssNS.GenerateFilesRequest": "Cannot convert the "fssNS.GenerateFilesRequest" value of type "fssNS.GenerateFilesRequest" to type "fssNS.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Generating files for Id 2
Cannot convert argument "req", with value: "fssNS.GenerateFilesRequest", for "GenerateFiles" to type "fssNS.GenerateFilesRequest": "Cannot convert the "fssNS.GenerateFilesRequest" value of type "fssNS.GenerateFilesRequest" to type "fssNS.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
**Generating files for Id 3**
Generating files for Id 4
Cannot convert argument "req", with value: "fssNS.GenerateFilesRequest", for "GenerateFiles" to type "fssNS.GenerateFilesRequest": "Cannot convert the "fssNS.GenerateFilesRequest" value of type "fssNS.GenerateFilesRequest" to type "fssNS.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument Generating files for Id 8 Cannot convert argument "req", with value: "fssNS.GenerateFilesRequest", for "GenerateFiles" to type "fssNS.GenerateFilesRequest": "Cannot convert the "fssNS.GenerateFilesRequest" value of type "fssNS.GenerateFilesRequest" to type "fssNS.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
**Generating files for Id 9**
**Generating files for Id 10**
**Generating files for Id 11**
Generating files for Id 12
Cannot convert argument "req", with value: "fssNS.GenerateFilesRequest", for "GenerateFiles" to type "fssNS.GenerateFilesRequest": "Cannot convert the "fssNS.GenerateFilesRequest" value of type "fssNS.GenerateFilesRequest" to type "fssNS.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Sometimes, most calls pass, and only the odd-one fails.
Here's another version of the script, which uses the auto-generated namespaces from New-WebServiceProxy:
$sqlServer=$args[0]
function CallFSS($Id) {
$uri = "http://localhost:1234/FileSystemService.svc?wsdl"
$srv = New-WebServiceProxy -Uri $uri
$type = $srv.GetType().Namespace
$datatype = ($type + '.GenerateFilesRequest')
$req = New-Object($datatype)
$req.Id = $Id
$response = $srv.GenerateFiles($req)
}
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=" + $sqlServer + ";Database=MyDatabase;Integrated Security=True"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "select Id FROM MyTable WHERE Status = 2"
$SqlCmd.Connection = $SqlConnection
$sqlResult = $SqlCmd.ExecuteReader()
while ($sqlResult.Read()) {
$Id = $sqlResult.GetInt32(0)
Write-Host Generating files for Id $Id
CallFSS $Id
}
$SqlCmd.Dispose()
$SqlConnection.Dispose()
$SqlConnection.Close()
Again, the results are inconsistent, though the error I get now relates to the auto-generated namespace:
**Generating files for Id 1**
**Generating files for Id 2**
Generating files for Id 3
Cannot convert argument "req", with value: "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest", for "GenerateFiles" to type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest": "Cannot convert the "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest" value of type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest" to type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Generating files for Id 4
Cannot convert argument "req", with value: "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest", for "GenerateFiles" to type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest": "Cannot convert the "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest" value of type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest" to type "Microsoft.PowerShell.Commands.NewWebserviceProxy.AutogeneratedTypes.WebServiceProxy3234_FileSystemService_svc_wsdl.GenerateFilesRequest"." At C:\SolutionPath\GENERATE_FILES.ps1:23 char:2
+ $response = $srv.GenerateFiles($req)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
Etc... execution is different every time.
I dearly hope this is something I'm doing wrong / misunderstanding as I am about to entirely give up on PowerShell. Is this maybe a caching issue? Any help would be greatly appreciated.
Ok, I found something :)
I added an output for the GenerateFiles method definition within the CallFSS function:
$srv | gm -Name GenerateFiles | select -ExpandProperty Definition
Foreach successful request, the output was
void GenerateFiles(fssNS.GenerateFilesRequest req)
The definition was different if I encountered an error:
void GenerateFiles(fssNS.GenerateFilesRequest, oehlvn0y,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null req)
So if you create the object using the full qualified name, it should work:
function CallFSS($Id)
{
$uri = "http://localhost:11662/Service1.svc?wsdl"
$srv = New-WebServiceProxy -Uri $uri -Namespace fssNS -Class fssClass
# Get the definition of the GenerateFiles method
$definition = $srv | gm -Name GenerateFiles | select -ExpandProperty Definition
# Extract the full qualified type name of the first parameter
$paramType = [regex]::Match($definition, 'GenerateFiles\((.*)\s\w+').Groups[1].Value
$bar = new-object $paramType
$bar.Id = $Id
$response = $srv.GenerateFiles($bar)
}
Note: This solution only works for methods with one parameter due to the regex. However, here is an implementation I would recommend:
function Invoke-FSS # Invoke is a valid Verb (see Get-Verb)
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
[int]$Id
)
Begin
{
$uri = "http://localhost:11662/Service1.svc?wsdl"
$srv = New-WebServiceProxy -Uri $uri -Namespace fssNS -Class fssClass
# Get the definition of the GenerateFiles method
$definition = $srv | gm -Name GenerateFiles | select -ExpandProperty Definition
# Extract the full qualified type name of the first parameter
$paramType = [regex]::Match($definition, 'GenerateFiles\((.*)\s\w+').Groups[1].Value
}
Process
{
$bar = new-object $paramType
$bar.Id = $Id
$response = $srv.GenerateFiles($bar)
}
End
{
$srv.Dispose()
}
}
In your example, you would invoke the method by piping the ids to the Invoke-FFS method:
$ids = #()
while ($sqlResult.Read()) {
$ids += $sqlResult.GetInt32(0)
}
$ids | Invoke-FSS
The Begin{} block gets called only once (to initialize your proxy) and the Process{} block gets called for each item in $ids. Finally, the End{} block gets called once at the end to dispose the proxy gracefully.
Related
I have the following script in PowerShell which has been executing fine for months. I did not change anything, but I am receiving the error below. I've tried on other machines, but I keep receiving the same error. Any assistance or guidance would be greatly appreciated. I would have to get our IT involved for any registry or permission changes, and I would have to walk them through the process as well.
Script
# start Excel
$excel = New-Object -comobject Excel.Application
$ms_access = New-Object -comobject Access.Application
function Test-IsFileAvailable {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[string]$Path
)
if (!(Test-Path -Path $Path -PathType Leaf)) {
# the file is not found
Write-Verbose "File '$Path' not found."
return $false
}
try {
$file = New-Object System.IO.FileInfo $Path
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
Write-Verbose "File '$Path' is not locked."
return $true
}
catch {
# file is locked by a process.
Write-Verbose "File '$Path' is locked by another process."
return $false
}
}
#set path files
$FilePath_raw = 'N:\DATA\StatusReport_Raw.xlsx'
$FilePath_report = 'N:DATA\StatusReport.xlsx'
$FilePath_date = 'N:\DATA\ReportDate.xlsx'
$FilePath_access = "N:\DATA\Access\Processing.accdb"
if (!(Test-IsFileAvailable $FilePath_raw -Verbose)) { exit }
if (!(Test-IsFileAvailable $FilePath_report -Verbose)) { exit }
if (!(Test-IsFileAvailable $FilePath_date -Verbose)) { exit }
if (!(Test-IsFileAvailable $FilePath_access -Verbose)) { exit }
#make it visible ($true) or invisible ($false)
$excel.Visible = $false
$wb_eval1 = $excel.Workbooks.Open($FilePath_raw)
$ws_eval1 = $wb_eval1.sheets.Item(1)
$wb_eval2 = $excel.Workbooks.Open($FilePath_report)
$ws_eval2 = $wb_eval2.sheets.Item(1)
$wb_date = $excel.Workbooks.Open($FilePath_date)
$ws_date = $wb_date.sheets.Item(1)
$ws_eval2.Cells.Clear()
$lrow2 = $ws_eval1.usedRange.Rows.Count
$range2=$ws_eval1.Range("A3:AA$lrow2")
$range2.copy()
$cpy_range_eval = $ws_eval2.Range("A1")
$ws_eval2.Paste($cpy_range_eval)
$date_range = $ws_eval1.Range("B1")
$date_range.copy()
$cpy_range_date = $ws_date.Range("A2")
$ws_date.Paste($cpy_range_date)
$wb_date.Save()
$wb_date.Close()
$wb_eval2.Save()
$wb_eval2.Close()
$wb_eval1.Close()
$ms_access.OpenCurrentDatabase($Filepath_access)
$ms_access.Run("ExportExcel")
$excel.Quit()
$ms_access.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb_eval1) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb_eval2) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb_date) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ms_access) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
$excel = $ms_access = $null
$wshell = New-Object -ComObject Wscript.Shell
$output = $wshell.Popup("The task has finished")
Add-Type -AssemblyName System.Windows.Forms
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
$balloon.BalloonTipText = 'The reports have been processed.'
$balloon.BalloonTipTitle = "Attention $Env:USERNAME"
$balloon.Visible = $true
$balloon.ShowBalloonTip(20000)
Error Message
Set-ExecutionPolicy : Windows PowerShell updated your execution policy successfully, but the setting is overridden by a policy defined at a more specific scope. Due to the override, your
shell will retain its current effective execution policy of RemoteSigned. Type "Get-ExecutionPolicy -List" to view your execution policy settings. For more information please see "Get-Help
Set-ExecutionPolicy".
At line:1 char:46
+ ... -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'N ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (:) [Set-ExecutionPolicy], SecurityException
+ FullyQualifiedErrorId : ExecutionPolicyOverride,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand
VERBOSE: File 'N:\DATA\StatusReport_Raw.xlsx' is not locked.
VERBOSE: File 'N:\DATA\StatusReport.xlsx' is not locked.
VERBOSE: File 'N:\DATA\ReportDate.xlsx' is not locked.
VERBOSE: File 'N:\DATA\Access\Processing.accdb' is not locked.
True
True
True
Exception calling "OpenCurrentDatabase" with "1" argument(s): "Unable to cast COM object of type
'Microsoft.Office.Interop.Access.ApplicationClass' to interface type 'Microsoft.Office.Interop.Access._Application'.
This operation failed because the QueryInterface call on the COM component for the interface with IID
'{68CCE6C0-6129-101B-AF4E-00AA003F0F07}' failed due to the following error: Error loading type library/DLL. (Exception
from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY))."
At N:\RUN\RUN.ps1:82 char:1
+ $ms_access.OpenCurrentDatabase($Filepath_access)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidCastException
Exception calling "Run" with "1" argument(s): "Unable to cast COM object of type
'Microsoft.Office.Interop.Access.ApplicationClass' to interface type 'Microsoft.Office.Interop.Access._Application'.
This operation failed because the QueryInterface call on the COM component for the interface with IID
'{68CCE6C0-6129-101B-AF4E-00AA003F0F07}' failed due to the following error: Error loading type library/DLL. (Exception
from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY))."
At N:\RUN\RUN.ps1:83 char:1
+ $ms_access.Run("ExportExcel")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidCastException
Exception calling "Quit" with "0" argument(s): "Unable to cast COM object of type
'Microsoft.Office.Interop.Access.ApplicationClass' to interface type 'Microsoft.Office.Interop.Access._Application'.
This operation failed because the QueryInterface call on the COM component for the interface with IID
'{68CCE6C0-6129-101B-AF4E-00AA003F0F07}' failed due to the following error: Error loading type library/DLL. (Exception
from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY))."
At N:\RUN\RUN.ps1:86 char:1
+ $ms_access.Quit()
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidCastException
So if I am understanding the problem the solution should be quite simple. Before you run the Powershell script right click on it and hit run as administrator. The reason the error is occurring is that you need escalated privileges to set the execution policy. As for the other issue it may be that those files are now protected and therefore unable to load so by using the run as administration option it should allow it to access those files.
I have had several issue in the past where just running with escalated permissions has fixed problems. Now I would not do this as a permanent fix but if this works it would at least get it working again.This is why you shouldn't
In .Net we can get the datasource from a connectionstring using below mechanism:
System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder(connectionString);
string server = builder.DataSource;
I was trying to do that in PowerShell but getting the following exception:
$ConstringObj = New-Object System.Data.SqlClient.SqlConnectionStringBuilder($conString)
New-Object : Exception calling ".ctor" with "1" argument(s): "Keyword
not supported: 'metadata'." At line:1 char:17
+ $ConstringObj = New-Object System.Data.SqlClient.SqlConnectionStringBuilder($con ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
How to do that in PowerShell?
Problem
There's some weird behavior when using SqlConnectionStringBuilder in PowerShell - let me explain
Since it's a dotnet class, you'd expect all of the same properties and methods available in C#
For example, this works fine in C#:
var cnnBuilder = new SqlConnectionStringBuilder();
cnnBuilder.DataSource = "server_name";
cnnBuilder.InitialCatalog = "db_name";
So the equivalent code in PS, should work:
$cnnBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$cnnBuilder.DataSource = "server_name"
$cnnBuilder.InitialCatalog = "db_name"
However, SqlConnectionStringBuilder is built ontop of DbConnectionStringBuilder which implements IDictionary so fundamentally we're working with a dictionary object that has some syntactic sugar wrappers
.NET resolves this with an override on the dictionary accessors and setters like this (simplified here):
public override object this[string keyword] {
get {
Keywords index = GetIndex(keyword);
return GetAt(index);
}
set {
Keywords index = GetIndex(keyword);
switch(index) {
case Keywords.DataSource: DataSource = ConvertToString(value); break;
case Keywords.InitialCatalog: InitialCatalog = ConvertToString(value); break;
// ***
}
}
}
So really, it's taking the DataSource property and mapping it to the "Data Source" key (with space)
Whenever PS assigns or retrieves a value, it has to decide whether to use the underlying dictionary implementation or the property. And when you look for DataSource in the dictionary (without the space), that sql connection keyword doesn't exist.
Solutions
Opt 1 - Use Dictionary Names
You can use the bracket or dot notation with the actual sql key to access the entry in the hashtable
$cnnBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$cnnBuilder["Data Source"] = "server_name"
$cnnBuilder."Initial Catalog" = "db_name"
Opt 2 - Use PSBase
PSBase returns the "raw view of the object" and will give us the default behavior in dotnet
$cnnBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$cnnBuilder.PSBase.DataSource = "server_name"
$cnnBuilder.PSBase.InitialCatalog = "db_name"
Opt 3 - Use -Property Parameter
During the construction, you can set the -Property parameter on New-Object which "sets property values and invokes methods of the new object."
$cnnBuilder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder `
-Property #{
DataSource = "server_name"
InitialCatalog = "db_name"
}
Additional Reading
Using SQLConnection object in PowerShell
Your example should work. However, you could also grab the datasource using a regex:
[regex]::Match($ConstringObj, 'Data Source=([^;]+)').Groups[1].Value
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.
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
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()
}