As shown in the code below I end up creating a new PSObject for each site in the sites collection. Eventually I need to run this for multiple SharePoint web applications each containing several sites, with the total sites crossing 2000. So that is a lot of PSObjects being created.
The PSObject does not have a dispose function so what are my options to make the code more efficient and dispose the object.
Also each time i execute the operation $SiteInfoCollection += $object there is reallocation of memory since it is a dynamic expanding array. According to some article i read several months ago any operation of memory reallocation is expensive and not good for performance. Is there a way to make this step efficient as well?
$SPWebApp = Get-SPWebApplication "http://portal.contoso.com"
$SiteInfoCollection = #()
foreach($site in $SPWebApp.sites)
{
$object = New-Object PSObject
Add-Member -InputObject $object -MemberType NoteProperty -Name URL -Value ""
Add-Member -InputObject $object -MemberType NoteProperty -Name Title -Value ""
Add-Member -InputObject $object -MemberType NoteProperty -Name Template -Value ""
$object.url = $site.url
$object.title = $site.rootweb.title
$object.template = $site.rootweb.WebTemplate
$SiteInfoCollection += $object
#$object.Dispose() #NOT valid operation
}
$site.Dispose()
}
$SiteInfoCollection
This object will be garbage collected when it's time to do so. It's not a heavy SharePoint object that contains COM & Database connections and there for doesn't need to be disposed.
You want to dispose $site so put in inside the foreach loop.
For most SharePoint PowerShell command you can use -AssignmentCollections instead of manually disposing:
Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
A more PowerShell way to get the same functionallity would be
$gc = Start-SPAssignment
Get-SPSite "http://portal.contoso.com" -Limit All -AssignmentCollection $gc | % {
# Loop
}
Stop-SPAssignment -Identity $gc
Related
The following requirement(s) I have:
As domain admin user logged on to an administrative client machine
I want to perform some changes on an exchange server using calls
from vba(excel 2013) via powershell to an exchange server (2013).
The client machine runs Windows 10 (1809) and powershell v5.1.17763.1
Upon a button press in the vba excel form I want to perform a trivial
task like getting all info for a specific mailbox user, reading
the results back in from stdout/stderr using WSH.Shell, later on more to come.
Executing the command below does what it shall, with the following two drawbacks:
1) the credentials are still asked again for though already passed to the ScriptBlock as $Cred via -ArgumentList
2) the powershell window does not close automatically after processing, it needs
to be closed actively by the user
Finally, the retrieved stdout/stderr gets me what I want (by the way, is there a direct connection possible as to have the powershell objects retrieved into a vba collection?)
WORKS on commandline (a "one-liner"), yet have to provide credentials via popup:
powershell -Command { $Username = 'MYDOMAIN\Administrator'; $Password = 'foobar'; $pass = ConvertTo-SecureString -AsPlainText $Password -Force; $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass; Invoke-Command -ComputerName Exchange -ArgumentList $Cred -ScriptBlock { $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange.somewhere.com/PowerShell/ -Authentication Kerberos -Credential $Cred; Import-PSSession $Session; Get-Mailbox MYUSER; Remove-PSSession $Session } }
WORKS from vba via WSH.Shell Exec, yet have to provide credentials via popup and have to actively close the powershell console window
(and see me avoiding double quotes (") within the powershell script, havent figured out yet how to escape them correctly ("" doesnt work)):
powershell -Command "& { $Username = 'MYDOMAIN\Administrator'; $Password = 'foobar'; $pass = ConvertTo-SecureString -AsPlainText $Password -Force; $Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass; Invoke-Command -ComputerName Exchange -ArgumentList $Cred -ScriptBlock { $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange.somewhere.com/PowerShell/ -Authentication Kerberos -Credential $Cred; Import-PSSession $Session; Get-Mailbox MYUSER; Remove-PSSession $Session } }"
So basically it is writing
powershell -Command "& { ... }"
in vba when called via 'wsh shell exec' instead of
powershell -Command { ... }
on the commandline, this seems to be required to retrieve stdin/stdout correctly, would be glad for suggestions why this is the case or if there is an alternative style to write this, too.
Any suggestions how to get rid of the powershell popup asking for the credential
and how to get rid of the powershell window not going away automatically?
Thanks,
Jeff
P.S.:
For your reference, the vba method to do the powershell call (End Function and #if stuff is broken in the code block, you'll figure it out though):
Public Function execPSCommand(ByVal psCmd As String, Optional ByVal label As String = "Debug") As String()
Dim oShell, oExec
Dim retval(2) As String
retval(0) = ""
retval(1) = ""
Set oShell = CreateObject("WScript.Shell")
Set oExec = oShell.Exec(psCmd)
oExec.stdin.Close ' close standard input before reading output
Const WshRunning = 0
Do While oExec.Status = WshRunning
Sleep 100 ' Sleep a tenth of a second
Loop
Dim stdout As String
Dim stderr As String
stdout = oExec.stdout.ReadAll
stderr = oExec.stderr.ReadAll
retval(0) = stdout
retval(1) = stderr
execPSCommand = retval()
End Function
' Sleep (fractions of a second, milliseconds to be precise) for VBA
'#If VBA7 Then
' Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'#Else
' Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
'#End If
I think you are not passing $cred argument properly to the scriptblock. The scriptblock should start with param($cred) if you want to use that local variable. Why not define $cred inside the scriptblock though? You can also use Using modifier to push local variable to the remote command (like $Using:cred, see more details https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_variables?view=powershell-6 )
Regarding exiting powershell at the end, I guess you can just type "Exit" or "Stop-Process $pid" at the end of your command.
#Mike: Great stuff, thanks a lot for pointing me into the right direction.
The solution for me was to add "param([PSCredential]$Cred); " as suggested by you.
Of course I could have created $Cred inside the ScriptBlock as well ;)
Furthermore I remembered to have read somewhere that a PSSession should be
closed by a Remove-PSSession command afterwards in order to free up resources.
BUT: this approach clashed with the way I was busy waiting for the wsh shell command to finish (in vba) and then reading stdout/stderr from the process
expecting to close soon - so I removed the busy wait in vba for this particular
case (remote ps session).
It turned out that I did not need to call Remove-PSsession at all, cross-checked
that with a Start-Sleep 60 as the last command (instead of Remove-PSsession)
and executed "Get-PSsession -ComputerName exchange" once in a while on a
real powershell console while execution went on in vba; as soon as the 60 seconds
did pass the session was cleaned up automatically (no more sessions listed).
So the short story is: omit busy waiting when a remote PSSession is done in the ps script (something behind the scenes seems to already have removed that process or anything else not yet clear to me gets in the way in a blocking manner) - why oExec.Status is still left on WshRunning in the 'busy waiting
case' is beyond me, I was expecting it to be either WshFinished or WshFailed,
but that way it caused a blocking powershell window waiting forever.
Anyway, hardcoded vba password is gone as well, read in instead using an inputbox now,
happy powershelling may continue ;)
I'm pretty new to powershell (I just took a semester at our local college). I have a directory with a lot of image files (.WMF) constantly being updated and I need to write a script that will take these files, save them to a new directory, and rotate them 90 degrees with "_90.wmf" added to the ending. I have been searching for a while and came up with a little code that will rotate the image but I can't get it to save to the new directory. Any help?
if (Test-Path J:\CutRite\v90\Import\MV_Nest_PTX_copy)
{
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
foreach($file in (ls "J:\CutRite\v90\Import\MV_Nest_PTX_copy\*.wmf")){
$convertfile = new-object System.Drawing.Bitmap($file.Fullname)
$convertfile.rotateflip("Rotate90FlipNone")
$newfilname = ($file.Fullname)
$convertfile.Save($newfilname, "wmf")
$file.Fullname
}
}
else
{
Write-Host "Path not found."
}
You're nearly there, you just need to create the new filename correctly:
if (Test-Path J:\CutRite\v90\Import\MV_Nest_PTX_copy)
{
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
foreach($file in (ls "J:\CutRite\v90\Import\MV_Nest_PTX_copy\*.wmf")){
$convertfile = new-object System.Drawing.Bitmap($file.Fullname)
$convertfile.rotateflip("Rotate90FlipNone")
$newfilename = $file.Fullname.replace(".wmf","_90.wmf")
$convertfile.Save($newfilname)
}
}
You can use the replace method of System.String to change .wmf to _90.wmf and store that in the $newfilename variable, then save the image using that name.
Also, not sure why you're loading System.Windows.Forms - unless you're using it in a snippet you've not included you don't need it for this.
Update:
If you wanted to save to a different directory altogether:
$newfilename = "C:\path\to\new\dir\" + $file.name.replace(".wmf","_90.wmf")
So here is what I want to do:
I have a VBA-application that uses information of multiple open Word-2010 Documents. This works fine as long as all open Documents are opened within the same process. But I would like to open these Documents not manually but via Powershell and so far I have only been able to do so by starting a new instance of Word. Let's have a look at the code:
$isRunning = (Get-Process | Where-Object { $_.Name -eq "WINWORD" }).Count -gt 0
if ($isRunning) {
#Select open Word Process
**$wrd = # ???**
# Make Word Visible
$wrd.visible = $true
# Open a document
$doc = $wrd.documents.add($latest.Fullname)
}
else {
# Create Word Object
$wrd = New-object -com word.application
# Make Word Visible
$wrd.visible = $true
# Open a document
$doc = $wrd.documents.add($latest.Fullname)
}
The ??? mark the spot where I would like to Select a running instance of Word in which I can open my docs. But all examples I could find always invoke a new object by starting a separate instance of word.
Another workaround would be to change my VBA application to access Documents of different processes, but I don't know if that is even possible or how to do it.
Either way, any help would be highly appreciated.
Thanks.
You can get a running instance like this:
$word = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application')
Quick background: I am fairly new to PowerShell but am a well-versed C# dev. I have mixed feelings about PowerShell so far. I love it one day, hate it the next. Just when I think I've got it figured out, I get stumped for hours trial-and-error-ing some feature I think it should implement but doesn't.
I would like PowerShell to let me override an object's ToString() method (which it does) such that when an object is referenced inside a double-quoted string, it will call my custom ToString method (which it does not).
Example:
PS C:\> [System.Text.Encoding] | Update-TypeData -Force -MemberType ScriptMethod -MemberName ToString -Value { $this.WebName }
PS C:\> $enc = [System.Text.Encoding]::ASCII
PS C:\> $enc.ToString() ### This works as expected
us-ascii
PS C:\> "$enc" ### This calls the object's original ToString() method.
System.Text.ASCIIEncoding
How does one define the variable expansion behavior of a .NET object?
The language specification doesn't say anything about overriding variable substitution, so I don't think there's a defined way of doing exactly what you want. If a single class was involved you could subclass it in C#, but for a class hierarchy the nearest I can think of is to produce a wrapper around the object which does have your desired behaviour.
$source = #"
using System.Text;
public class MyEncoding
{
public System.Text.Encoding encoding;
public MyEncoding()
{
this.encoding = System.Text.Encoding.ASCII;
}
public MyEncoding(System.Text.Encoding enc)
{
this.encoding = enc;
}
public override string ToString() { return this.encoding.WebName; }
}
"#
Add-Type -TypeDefinition $Source
Then you can use it like this:
PS C:\scripts> $enc = [MyEncoding][System.Text.Encoding]::ASCII;
PS C:\scripts> "$enc"
us-ascii
PS C:\scripts> $enc = [MyEncoding][System.Text.Encoding]::UTF8;
PS C:\scripts> "$enc"
utf-8
PS C:\scripts> $enc.encoding
BodyName : utf-8
EncodingName : Unicode (UTF-8)
HeaderName : utf-8
WebName : utf-8
WindowsCodePage : 1200
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.EncoderReplacementFallback
DecoderFallback : System.Text.DecoderReplacementFallback
IsReadOnly : True
CodePage : 65001
If you don't like the extra .encoding to get at the underlying object you could just add the desired properties to the wrapper.
I stumbled upon a semi-solution, and discovered yet another strange behavior in Powershell. It appears that when string expansion involves a collection, the user-defined ScriptMethod definition of the ToString() method is called, as opposed to the object's original ToString() method.
So, using the above example, all that is necessary is adding one lousy comma:
PS C:\> [System.Text.Encoding] | Update-TypeData -Force -MemberType ScriptMethod -MemberName ToString -Value { $this.WebName }
PS C:\> $enc = ,[System.Text.Encoding]::ASCII # The unary comma operator creates an array
PS C:\> "$enc" ### This now works
us-ascii
This seems like a bug to me.
Are the routines for serializing and deserializing objects from PowerShell (as performed by PowerShell Remoting) available?
I'd like to avoid having to write the objects to disk (with Export-CliXML) and reading it back in with (Import-CliXML).
Basically, I want to get the property bags that the deserialization creates so that I can add them to an AppFabric object cache. Otherwise, AppFabric tries to use .NET serialization, which fails for a number of the standard object types.
Perhaps through the $host or $executioncontext variables?
They have published the PowerShell Remoting Specification which would give you the spec, but the source code they used to implement it is not public at this time. http://msdn.microsoft.com/en-us/library/dd357801(PROT.10).aspx
Oh, I see what you're asking, you're looking for a ConvertTo-CliXml similar to how ConvertTo-Csv works in place of Export-Csv. At first glance it sounds like you're trying to avoid CliXml entirely.
In that case, there's one on PoshCode: ConvertTo-CliXml ConvertFrom-CliXml
Here's a verbatim copy to give you an idea (I haven't checked this for correctness):
function ConvertTo-CliXml {
param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[PSObject[]]$InputObject
)
begin {
$type = [PSObject].Assembly.GetType('System.Management.Automation.Serializer')
$ctor = $type.GetConstructor('instance,nonpublic', $null, #([System.Xml.XmlWriter]), $null)
$sw = New-Object System.IO.StringWriter
$xw = New-Object System.Xml.XmlTextWriter $sw
$serializer = $ctor.Invoke($xw)
$method = $type.GetMethod('Serialize', 'nonpublic,instance', $null, [type[]]#([object]), $null)
$done = $type.GetMethod('Done', [System.Reflection.BindingFlags]'nonpublic,instance')
}
process {
try {
[void]$method.Invoke($serializer, $InputObject)
} catch {
Write-Warning "Could not serialize $($InputObject.GetType()): $_"
}
}
end {
[void]$done.Invoke($serializer, #())
$sw.ToString()
$xw.Close()
$sw.Dispose()
}
}