I'm trying to update a node title on my SharePoint site with PowerShell:
$web = Get-SPWeb "http://dev:18792/sites/devsite/"
foreach ($webs in $web.Webs)
{
$nodes = $webs.Navigation.QuickLaunch
foreach ($node in $nodes)
{
$nodeTitle = $node.Title
if ($nodeTitle.ToString().Contains("First node"))
{
foreach ($nodeChild in $node.Children) {
$nodeChildTitle = $nodeChild.Title
if ($nodeChildTitle.ToString().Contains("Original childnode title")) {
$nodeChild.Title = "Changed title"
$nodeChild.Update()
Write-Host "done."
}
}
}
$webs.Dispose()
}
$web.Dispose()
}
If I try it a second time and change
.Contains("Original childnode title")
to:
.Contains("Changed title")
It does enter that if statement, but the changes is not visible in the GUI.
Am I missing something here?
Could it maybe be because you have the Publishing features activated? Manipulating the quick launch in a publishing site is a bit different. Here's some sample C# code:
PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(oWeb);
SPNavigationNodeCollection nodes = pubWeb.Navigation.CurrentNavigationNodes;
foreach (SPNavigationNode node in nodes)
{
if (node.Title == "Original Title")
{
node.Title = "New Title";
node.Update();
break;
}
}
Otherwise try calling the $webs.Update() method
Related
How do I call methods within workflow?
I am trying to call "Task" from within a workflow and it seems to be getting ignored? I have provided a watered down nonsense code to illustrate what I'm trying to do. basically call a method within the class in parallel and return the results.
Sample Code
Class Something {
[string]Task($item) {
Start-sleep -Seconds 10
Return "Result"
}
[System.Array]GetSomething($list) {
workflow GetWF {
param($listarr)
ForEach -parallel ($item in $listarr) {
$res = InlineScript {
Write-Host("Starting.." + $using:item)
$this.Task($using:item)
}
}
$res
}
Return GetWF -listarr $list
}
}
$list = #('host1','host2','host3','host4')
$Something = [Something]::New()
$Something.GetSomething($list)
Output :
Starting..host1
Starting..host4
Starting..host2
Starting..host3
Desired Result from Example
The issue is how do I get my results back as an array ? In this example above I would like to see the final result to be this:
$Result = #("Result","Result","Result","Result")
I am working on implementing a singleton class to store some regularly accessed status information for my script, including hacking around the issue of $myInvocation only being populated in the main script. All working as planned with this.
class pxStatus {
static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
pxStatus ([string]$path) {
if ([pxStatus]::singleton -eq $null) {
$this.Path = $path
[pxStatus]::singleton = $this
} else {
Throw "Singleton already initialized"
}
}
static [pxStatus] Get() {
if ([pxStatus]::singleton -eq $null) {
Throw "Singleton not yet initialized"
} else {
return [pxStatus]::singleton
}
}
}
CLS
[void]([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent)))
([pxStatus]::Get()).StartTime
([pxStatus]::Get()).Context
([pxStatus]::Get()).Path
With one exception. Even with that [void] on the [pxStatus]::New() line, I am getting a blank line in the console. Even $null = ([pxStatus]::New((Split-Path ($myInvocation.myCommand.path) -parent))) is echoing a blank line to the console. And for the life of me I can't see what is causing it.
It's not new that causes a blank line but ([pxStatus]::Get()).StartTime.
To fix the issue, you may output it as string, i.e. not formatted, e.g. ([pxStatus]::Get()).StartTime.ToString()
You problem has already been diagnosed, but I wanted to take a second to show how to actually implement a singleton-like type in PowerShell (see inline comments):
class pxStatus {
# hide backing field from user
hidden static [pxStatus] $singleton = $null
[string]$Context = 'machine'
[string]$Path = $null
[datetime]$StartTime = (Get-Date)
# hide instance constructor, no one should call this directly
hidden pxStatus ([string]$path) {
# Only allow to run if singleton instance doesn't exist already
if ($null -eq [pxStatus]::singleton) {
$this.Path = $path
} else {
Throw "Singleton already initialized - use [pxStatus]::Get()"
}
}
# Use a static constructor to initialize singleton
# guaranteed to only run once, before [pxStatus]::Get() or [pxStatus]::singleton
static pxStatus () {
# grab the path from context, don't rely on user input
if(-not $PSScriptRoot){
throw "[pxStatus] can only be used in scripts!"
}
# this will only succeed once anyway
[pxStatus]::singleton = [pxStatus]::new($PSScriptRoot)
}
static [pxStatus] Get() {
# No need to (double-)check ::singleton, static ctor will have run already
return [pxStatus]::singleton
}
}
[pxStatus]::Get().StartTime
I am refactoring some function based XML reader code to class methods, and seeing some issues. With the function, I can run a test and verify the XML loaded right, then change the XML and test for error conditions. But this class based approach fails due to "the file is open in another program", forcing me to close the console before I can revise the XML.
Initially I was using the path directly in the xmlReader. So I moved to a StreamReader input to the xmlReader. And I even played with creating an all new xmlDocument and importing the root node of the loaded XML into that new xmlDocument. None works.
I suspect the reason the function based version works is because the xmlReader variable is local scope, so it goes out of scope when the function completes. But I'm grasping at straws there. I also read that Garbage Collection could be an issue, so I added [system.gc]::Collect() right after the Dispose and still no change.
class ImportXML {
# Properties
[int]$status = 0
[xml.xmlDocument]$xml = ([xml.xmlDocument]::New())
[collections.arrayList]$message = ([collections.arrayList]::New())
# Methods
[xml.xmlDocument] ImportFile([string]$path) {
$importError = $false
$importFile = ([xml.xmlDocument]::New())
$xmlReaderSettings = [xml.xmlReaderSettings]::New()
$xmlReaderSettings.ignoreComments = $true
$xmlReaderSettings.closeInput = $true
$xmlReaderSettings.prohibitDtd = $false
try {
$streamReader = [io.streamReader]::New($path)
$xmlreader = [xml.xmlreader]::Create($streamReader, $xmlReaderSettings)
[void]$importFile.Load($xmlreader)
$xmlreader.Dispose
$streamReader.Dispose
} catch {
$exceptionName = $_.exception.GetType().name
$exceptionMessage = $_.exception.message
switch ($exceptionName) {
Default {
[void]$this.message.Add("E_$($exceptionName): $exceptionMessage")
$importError = $true
}
}
}
if ($importError) {
$importFile = $null
}
return $importFile
}
}
class SettingsXML : ImportXML {
# Constructor
SettingsXML([string]$path){
if ($this.xml = $this.ImportFile($path)) {
Write-Host "$path!"
} else {
Write-Host "$($this.message)"
}
}
}
$settingsPath = '\\Mac\iCloud Drive\Px Tools\Dev 4.0\Settings.xml'
$settings = [SettingsXML]::New($settingsPath)
EDIT:
I also tried a FileStream rather than a StreamReader, with FileShare of ReadWrite, like so
$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read
$fileShare = [System.IO.FileShare]::ReadWrite
$fileStream = New-Object -TypeName System.IO.FileStream $path, $fileMode, $fileAccess, $fileShare
Still no luck.
I think you're on the right lines with Dispose, but you're not actually invoking the method - you're just getting a reference to it and then not doing anything with it...
Compare:
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose
OverloadDefinitions
-------------------
void Dispose()
void IDisposable.Dispose()
PS> _
with
PS> $streamReader = [io.streamReader]::New(".\test.xml");
PS> $streamReader.Dispose()
PS> _
You need to add some () after the method name so your code becomes:
$xmlreader.Dispose()
$streamReader.Dispose()
And then it should release the file lock ok.
I am trying to parse values from an XML file that will add items to a collection using a foreach loop, then add the items from that collection to another collection using another foreach loop with an addition value. This is what I am doing so far:
[xml]$testResults = Get-Content -Path $testResultsPath
$resultsByName = #{}
$resultsByPhone = #{}
$loop = 0
foreach($testCase in $testResults.'test-results'.'test-suite')
{
foreach($testCase in $testResults.'test-results'.'test-suite'[$loop].'results'.'test-suite'.'results'.'test-suite'.'results'.
'test-suite'.'results'.'test-suite'.'results'.'test-suite'.'results'.'test-suite'.'results'.'test-case')
{
$NameWithPone = $testCase.name.ToUpper().Substring($testCase.name.LastIndexOf('.')+1);
$Name =$NameWithPone.Substring(0, $NameWithPone.IndexOf('_'));
$PhoneVersion = $testCase.name.Substring($testCase.name.IndexOf('_')+1);
$resultsByName.Add($PhoneVersion, $Name)
Foreach($resultCase in $resultsByName)
{
$resultsByPhone.Add($resultsByName, $testCase.result)
}
}
$loop++
}
But this will only add it first result, then give the error "Item has already been added. Key in dictionary:
'System.Collections.Hashtable' Key being added: 'System.Collections.Hashtable'" I think this is because I am adding the same item each time, how can I correct this?
The first collection will look like this:
google_pixel_xl-7_1_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
htc_10-6_0_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
oneplus_one-4_4_4 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY
But I want to add both values together to another collection which would look like:
google_pixel_xl-7_1_1 TESTTHATAREGISTEREDUSERCANLOGINTOTHECUSTOMERAPPSUCCESSFULLY Error
I got this done by doing this:
[xml]$testResults = Get-Content -Path $testResultsPath
foreach($testCase in $testResults.'test-results'.'test-suite')
{
function Get-TestCases($myResults)
{
$testCases = #()
foreach($child in $myResults.ChildNodes)
{
if($child.'test-case' -eq $null)
{
foreach($testCase in Get-TestCases $child)
{
$testCases += $testCase
}
}
else
{
$testCases += $child.'test-case'
}
}
return $testCases
}
$tests = Get-TestCases $testResults.'test-results'.'test-suite'[$loop]
foreach($test in $tests)
{
$PhoneVersion = $test.name.Substring($test.name.IndexOf('_')+1);
$resultsByPhone.Add($PhoneVersion, #{})
$NameWithPhone = $test.name.ToUpper().Substring($test.name.LastIndexOf('.')+1);
$Name =$NameWithPhone.Substring(0, $NameWithPhone.IndexOf('_'));
$resultsByPhone[$phoneVersion].Add($Name, $test.result)
}
$resultsByPhone[$phoneVersion]
$resultsByPhone
$loop++
}
I am pretty new to programming and definitely to Zend/Lucene indexing. From what I can tell, though, my code is correct. I feel like I may be overlooking a step or something trying to upload changes and adds to the database so that they appear in the search on my website. I'm not getting any kind of an error message though. Below is the code from the controller. I guess let me know if you need anything else to help this make sense. Thanks in advance for any direction you can give.
class SearchController extends Zend_Controller_Action
{
public function init()
{
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
$this->view->identity = $auth->getIdentity();
}
}
public function indexAction()
{
// action body
}
public function buildAction()
{
// create the index
$index = Zend_Search_Lucene::open(APPLICATION_PATH . '/indexes');
$page = $this->_request->getParam('page');
// build product pages
if ($page == 'search') {
$mdl = new Model_Search();
$search = $mdl->fetchAll();
if ($search->count() > 0) {
foreach ($search as $s) {
$doc = new Zend_Search_Lucene_Document();
$doc->addField(Zend_Search_Lucene_Field::unIndexed('id', $s->id));
$doc->addField(Zend_Search_Lucene_Field::text('name', $s->name));
$doc->addField(Zend_Search_Lucene_Field::text('uri', $s->uri));
$doc->addField(Zend_Search_Lucene_Field::text('description', $s->description));
$index->addDocument($doc);
}
}
$index->optimize();
$this->view->indexSize = $index->numDocs();
}
}
public function resultsAction()
{
if($this->_request->isPost()) {
$keywords = $this->_request->getParam('query');
$query = Zend_Search_Lucene_Search_QueryParser::parse($keywords);
$index = Zend_Search_Lucene::open(APPLICATION_PATH . '/indexes');
$hits = $index->find($query);
$this->view->results = $hits;
$this->view->keywords = $keywords;
} else {
$this->view->results = null;
}
}
}
Lucene indexes won't stay in sync with your database automatically, you either need to rebuild the entire index or remove and re-add the relevant documents when they change (you cannot edit an existing document).
public function updateAction()
{
// something made the db change
$hits = $index->find("name: " . $name);
foreach($hits as $hit) {
$index->delete($hit->id)
}
$doc = new Zend_Search_Lucene_Document();
// build your doc
$index->add($doc);
}
Note that lucene documents had their own internal id property, and be careful not to mistake it for an id keyword that you provide.