Pass a Microsoft.SharePoint.SPList to a function - sharepoint

I'm pretty new to powershell & sharepoint and i'm having hard time trying to create a function.
I'm trying to make a generic function to add new items into a SPList but i can't pass the SPList to the function.
Here is my function prototype:
function Add-IntoList([Microsoft.SharePoint.SPList] $List,[hashtable] $Columns)
{
... some code
}
And here is my call to the function:
$web = Get-Web("http://some_url/sandbox1") #It returns the Get-SPWeb
$test = #{"Title" = "Olympia"; "Body" = "Salem"}
Add-IntoList($web.Lists["Announcements"], $test)
And it doesn't work, i can't see why.
Here is the error powershell tells me:
Add-IntoList : Cannot process argument transformation on parameter 'List'. Cannnot convert the "System.Object[]" value of type "System.Object[]" to type "Microsoft.Sharepoint.SPList".
What am i doing wrong ?
Thanks in advance,
Nicolas

When you call the function, instead of calling it like:
Add-IntoList($web.Lists["Announcements"], $test)
call it like:
Add-IntoList $web.Lists["Announcements"] $test

Related

How can a Powershell function specify a ComObject parameter type?

Let's say that I'm trying to write a Powershell function that prints a result set to an Excel worksheet, like this:
function Write-ToWorksheet {
param (
[Parameter( Position = 0, Mandatory = $true )]
[MyLibrary.MyCustomResultType[]]
$ResultSet,
[Parameter( Position = 1, Mandatory = $true )]
[Excel.Worksheet]
$Worksheet
)
# ... Implementation goes here ...
}
And let's say that I'm calling it in a way something like this:
$excel = New-Object -ComObject Excel.Application
$wb = $excel.Workbooks.Add()
$results = Get-MyResults # Never mind what this does.
Write-ToWorksheet -ResultSet $results -Worksheet $wb.Sheets[ 1 ]
And this code will almost work, except that it chokes on my type specification of [Excel.Worksheet].
I realize that it is not necessary to specify the parameter type, and that the code will work just fine without it, as this answer points out.
But to please my inner pedant, is there any way to constrain the parameter type using a reference to a COM object type like Excel.Worksheet?
The reason that PowerShell is complaining about your Excel.Worksheet type is because it's not the name of the true .NET class/interface.
The parameter type you'd need to specify is Microsoft.Office.Interop.Excel.Worksheet instead (once the Excel interop assembly has been loaded, either directly via Add-Type or after the call to New-Object -ComObject Excel.Application as that will load the desired library too)
With that said, I don't believe this will work as intended because of the way that PowerShell handles COM objects by creating a transparent COM adapter layer between the true type of the variable exposed in PowerShell.
Interestingly there appear to be differences in the way that PowerShell handles parameter conversions when supplying them via Named arguments vs Positional arguments as can be seen with my demo code below:
function Get-WorksheetName {
param (
[Parameter( Position = 1, Mandatory = $true )]
[Microsoft.Office.Interop.Excel.Worksheet]
$Worksheet
)
return $Worksheet.Name
}
Calling the function using named arguments fails:
Whereas calling the function via positional arguments works as expected:
If positional arguments aren't something you'd like to use, then another alternative would be to drop the parameter type constraint and instead check the type using the ValidateScript attribute instead. This still ensures type safety:
function Get-WorksheetName {
param (
[Parameter(Position = 1, Mandatory = $true)]
[ValidateScript({$_ -is [Microsoft.Office.Interop.Excel.Worksheet]})]
$Worksheet
)
return $Worksheet.Name
}
Passing a different type of object would result in this:

Evaluate variable(s) inside string passed escaped to function in PowerShell

I have a PowerShell function like this (I have simplified it here to make it more understandable):
Function QueryList
{
param(
[Parameter(Mandatory=$true,Position=1)]
[Microsoft.SharePoint.SPList] $list
[Parameter(Mandatory=$true,Position=2)]
[string] $camlQuery
)
$itemsQry = New-Object Microsoft.SharePoint.SPQuery
$itemsQry.Query = $camlQuery
$itemsQry.ViewFieldsOnly = $false
$itemsQry.RowLimit = 0
$itemsQry.ViewAttributes = "Scope='Recursive'"
return $list.GetItems($itemsQry)
}
Function MigrateList
{
param(
[Parameter(Mandatory=$true,Position=1)]
[Microsoft.SharePoint.SPList] $list,
[Parameter(Mandatory=$true,Position=2)]
[string] $matchingItemsQuery
)
foreach ($listItem in $list.Items)
{
# get items using the query (how to inject '$listItem' into query string?)
$targetItem = QueryList -list $list -camlQuery $matchingItemsQuery
# do something with matching items
...
}
}
# main script
$matchingItemsQuery = "<Where><Eq><FieldRef Name='Title' /><Value Type='TEXT'>`$(`$listItem[`"Title`"])</Value></Eq></Where>"
$targetItems = MigrateList -list $listXy -matchingItemsQuery $matchingItemsQuery
As you can see, I want to query some items from a list, matching a given criteria. As the criteria changes from list to list I want to be able to pass the query to the function, inside the query there is a reference to the variable that will only exist in the 'MigrateList' function (here: $listItem).
As it is now, the variable will of course not be evaluated to the objects value ("Title" column value of $listItem) as it is passed as a string, as the '$' are escaped (which is needed to pass the query to the function).
I know it is maybe not the nicest construct but it would get the job done. So how could I change the script that the passed query string will be injected with the $listItem object (in this case, column value)?
Thank you for the comments, especially #TheIncorrigible1 for the tip regarding using a scriptblock, which I (successfully) tried to implement.
I got it to work with this:
Function MigrateList
{
param(
[Parameter(Mandatory=$true,Position=1)]
[Microsoft.SharePoint.SPList] $list,
[Parameter(Mandatory=$true,Position=2)]
[scriptblock] $itemMatchQuerySb
)
foreach ($listItem in $list.Items)
{
# get items using the query (how to inject '$listItem' into query string?)
$matchingItemsQuery = (. $itemMatchQuerySb)
$targetItem = QueryList -list $list -camlQuery $matchingItemsQuery
# do something with matching items
...
}
}
# main script
$matchingItemsQuerySb = [scriptblock]::Create("echo ""<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>`$(`$listItem[`"Title`"])</Value></Eq></Where>""")
$targetItems = MigrateList -list $listXy -matchingItemsQuery $matchingItemsQuery

Can't locate object method "say_hello" via package "1"

I just started to learn Perl. When I moved to object orientation I am getting an error like
Can't locate object method "say_hello" via package "1" (perhaps you forgot to load "1"?) at ./main.pl line 8.
I googled a lot for a solution. Got some similar issues like this. My understanding is it's not a general issue.
Here is my class
# MyModule.pm
package MyModule;
use strict;
use warnings;
sub new {
print "calling constructor\n";
}
sub say_hello {
print "Hello from MyModule\n";
}
1;
Here is my test script
# main.pl
#!/usr/bin/perl -w
use strict;
use warnings;
use MyModule;
my $myObj = new MyModule();
$myObj->say_hello();
The code is working perfectly if remove last line from main.pl
Your constructor new needs to return a blessed reference to the data structure you are using to contain the object's information. You have no relevant data here, but you still need to return something
bless associates the data with a specific package. In this case, your object should be blessed into MyModule, so that perl knows to look for MyModule::say_hello when it sees a method call like $myObj->say_hello()
Your current constructor returns the value returned by the print statement, which is 1 if it succeeded, as it almost certainly does. That is why you see the "1" in the error message
Can't locate object method "say_hello" via package "1" (perhaps you forgot to load "1"?) at ./main.pl line 8.
The most common container for an object's data is a hash, so you need to change new to this
sub new {
print "calling constructor\n";
my $self = { };
bless $self, 'MyModule';
return $self;
}
and then your program will work as it should. It creates an anonymous hash and assigns it to the $self variable, then blesses and returns it
Note that this can be made much more concise:
Without a return statement, a subroutine will return the value of the most recently executed statement
By default, bless will bless the data into the current package
There is no need to store the reference in a variable before blessing it
So the same effect may be achieved by writing
sub new {
print "calling constructor\n";
bless { };
}
Note also that your call
my $myObj = new MyModule()
is less than ideal. It is called indirect object notation and can be ambiguous. It is better to always use a direct method reference, such as
my $myObj = MyModule->new()
so as to disambiguate the call
You're not creating a new object, and thus $myObj is just the return code of the "print" statement (or 1).
You need to bless something and return it.
sub new {
my ( $class ) = #_;
print "Calling Constructor\n";
my $self = {};
bless $self, $class;
return $self;
}
That way $myObj will actually be an object, not just a return code :)

How to solve the "String" in "for each" loop

My task is to open multiple files after a button clicked, and read all those selected files.
I have found an example of foreach function in c#, but I would like to write it in C++. How should I actually to convert that?
It shows the error that System::String, cannot use this type here without top level '^'.
I'm still new with that. Does anyone can give suggestion?
Thank you very much.
Below is my written codes
Stream^ myStream;
OpenFileDialog^ openFileDialog1 = gcnew OpenFileDialog;
openFileDialog1->InitialDirectory = "c:\\";
openFileDialog1->Title = "open captured file";
openFileDialog1->Filter = "CP files (*.cp)|*.cp|All files (*.*)|*.*|txt files (*.txt)|*.txt";
openFileDialog1->FilterIndex = 2;
//openFileDialog1->RestoreDirectory = true;
openFileDialog1->Multiselect = true;
if ( openFileDialog1->ShowDialog() == System::Windows::Forms::DialogResult::OK )
{
**for each (String line in openFileDialog1->FileName)**
System::Diagnostics::Debug::WriteLine("{0}",line);
myStream->Close();
}
Depending on the Container type returned, there is a std::for_each defined in <algorithm>-header.
The function Looks like this:
#include <algorithm>
std::for_each(InputIterator first, InputIterator last, Function fn);
You need to provide two iterators, one for the beginning of the Container and one for the end of the iterator. As a third Argument you can provide a function (function-object) and operate on the current argument.
See here for further reference.
As I got it from MSDN, the property SafeFileNames returns an array<String^>.
System::Array provides a static method called Array::ForEach. You would need to call it like
System::Action<T> myAction; // You will Need to provide a function here to be used on every filename.
System::Array::ForEach(openFileDialog1->SafeFileNames, myAction);
This comes closest to foreach from C#.
Look here for System::Array::ForEach and here for OpenFileDialog::SafeFileNames.

Setting ForceCheckout on an SPList

I'm trying to set the ForceCheckout property on an SPList item and it's just not taking. I'm calling the Update() command as required. All it should take, in essence, is the following two lines.
$myList.ForceCheckout = $false
$myList.Update()
Any ideas why this isn't working? It's remains $true no matter what.
Are you really using $myList, or are you doing something like:
$web.lists["foo"].forcecheckout = $false
$web.lists["foo"].update()
...because the above won't work. Each time you use the Lists collection with an indexer like this, you're getting a new instance of the list. The second line doesn't know about the first line's changes. Ensure you do:
$myList = $web.Lists["foo"]
$myList.forcecheckout = $false
$myList.update()
This will work because you're using the same instance.
-Oisin

Resources