Regularly check a property of a SharePoint item with Powershell - powershell

I am using powershell to check, if a SharePoint I just added has changed. To do so, I first add the item via
$spFile = $spFiles.Add(...)
after that I regularly do this:
$spFile.Item["propertyName"]
The result is the same every time, even if I change the property in SharePoint. So I guess I have to refresh the item in Powershell. How can this be done?

You need to get the document library once again and then only you can get the changed value. It stores in cache, Something like this you need to do
$library = $web.Lists["DocumentLibrary"]
$spFiles= $library.Items | where {$_.FileSystemObjectType -eq "File"}
foreach ($spFile in $spFiles) {
$spFile.Item["propertyName"]
}

Related

Getting target framework attribute in PowerShell Core

I'm looking for a way to retrieve the target framework attribute (e.g. .NETCoreApp,Version=v2.1) from a DLL when using PowerShell Core, ideally without loading the DLL directly into the main session.
I can do this in Windows PowerShell 5, as it has access to the ReflectionOnlyLoadFrom method...
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
[Reflection.Assembly]::ReflectionOnlyLoadFrom($dllPath).CustomAttributes |
Where-Object {$_.AttributeType.Name -eq 'TargetFrameworkAttribute'} |
Select -ExpandProperty ConstructorArguments |
Select -ExpandProperty value
However, I realise that this approach isn't available in .NET Core.
Editor's note: Even though the documentation (as of this writing) misleadingly suggests that the ReflectionOnlyLoadFrom method is available in .NET Core, it is not, as explained here.
From what I've seen, it looks likely that I should be able to access the custom attributes that hold the target framework attribute by using an instance of the System.Reflection.Metadata.MetadataReader class that's available in .NET Core (a couple of examples of this in use can be found here: https://csharp.hotexamples.com/examples/System.Reflection.Metadata/MetadataReader/GetCustomAttribute/php-metadatareader-getcustomattribute-method-examples.html ). However, all the constructors for this type seem to use a Byte* type, as the following shows when running from PowerShell Core:
([type] 'System.Reflection.Metadata.MetadataReader').GetConstructors() | % {$_.GetParameters() | ft}
I have no idea how to create a Byte* type in any version of PowerShell. Perhaps there's a method in System.Reflection.Metadata that I should be using before creating the MetadataReader object, but I haven't found it yet.
Apologies for the length of this question, but I'm hoping by sharing my notes I'll help in tracking down the solution. Any advice on how this target framework information can be obtained using PowerShell Core?
After quite a bit of work, I managed to put together a PowerShell script that works (without external dependencies) in PowerShell Core that pulls in the target framework from a DLL:
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
$stream = [System.IO.File]::OpenRead($dllPath)
$peReader = [System.Reflection.PortableExecutable.PEReader]::new($stream, [System.Reflection.PortableExecutable.PEStreamOptions]::LeaveOpen -bor [System.Reflection.PortableExecutable.PEStreamOptions]::PrefetchMetadata)
$metadataReader = [System.Reflection.Metadata.PEReaderExtensions]::GetMetadataReader($peReader)
$assemblyDefinition = $metadataReader.GetAssemblyDefinition()
$assemblyCustomAttributes = $assemblyDefinition.GetCustomAttributes()
$metadataCustomAttributes = $assemblyCustomAttributes | % {$metadataReader.GetCustomAttribute($_)}
foreach ($attribute in $metadataCustomAttributes) {
$ctor = $metadataReader.GetMemberReference([System.Reflection.Metadata.MemberReferenceHandle]$attribute.Constructor)
$attrType = $metadataReader.GetTypeReference([System.Reflection.Metadata.TypeReferenceHandle]$ctor.Parent)
$attrName = $metadataReader.GetString($attrType.Name)
$attrValBytes = $metadataReader.GetBlobContent($attribute.Value)
$attrVal = [System.Text.Encoding]::UTF8.GetString($attrValBytes)
if($attrName -eq 'TargetFrameworkAttribute') {Write-Output "AttributeName: $attrName, AttributeValue: $attrVal"}
}
$peReader.Dispose()
I'm mostly happy with it, the only issue I'd still like to sort out is that I'm getting some unhandled characters in the string output. I'll try to get rid of them.

Using querySelectorAll on an mshtml.HTMLDocumentClass object in PowerShell causes a crash

I'm trying to do some web-scraping via PowerShell, as I've recently discovered it is possible to do so without too much trouble.
A good starting point is to just fetch the HTML, use Get-Member, and see what I can do from there, like so:
$html = Invoke-WebRequest "https://www.google.com"
$html.ParsedHtml | Get-Member
The methods available to me for fetching specific elements appear to be the following:
getElementById()
getElementsByName()
getElementsByTagName()
For example I can get the first IMG tag in the document like so:
$html.ParsedHtml.getElementsByTagName("img")[0]
However after doing some more research in to whether I could use CSS Selectors or XPath I discovered that there are unlisted methods available, since we are just using the HTML Document object documented here:
querySelector()
querySelectorAll()
So instead of doing:
$html.ParsedHtml.getElementsByTagName("img")[0]
I can do:
$html.ParsedHtml.querySelector("img")
So I was expecting to be able to do:
$html.ParsedHtml.querySelectorAll("img")
...in order to get all of the IMG elements. All the documentation I've found and googling I've done supports this. However, in all my testing this function crashes the calling process and reports a heap corruption exception code in the Event Log (0xc0000374).
I'm using PowerShell 5 on Windows 10 x64. I've tried it in a Win10 x64 VM that is a clean build and just patched up. I've also tried it in Win7 x64 upgraded to PowerShell 5. I haven't tried it on anything prior to PowerShell 5 as all our systems here are upgraded, but I probably will once I have time to spool a new vanilla VM for testing.
Has anyone run in to this issue before? All my research so far is a dead end. Are there alternatives to querySelectorAll? I need to scrape pages that will have predictable sets of tags inside unpredictable layouts and potentially no IDs or classes assigned to the tags, so I want to be able to use selectors that allow structure/nesting/wildcards.
P.S. I've also tried using the InternetExplorer.Application COM object in PowerShell, the result is the same, except instead of PowerShell crashing Internet Explorer crashes. This was actually my original approach, here's the code:
# create browser object
$ie = New-Object -ComObject InternetExplorer.Application
# make browser visible for debugging, otherwise this isn't necessary for function
$ie.Visible = $true
# browse to page
$ie.Navigate("https://www.google.com")
# wait till browser is not busy
Do { Start-Sleep -m 100 } Until (!$ie.Busy)
# this works
$ie.document.getElementsByTagName("img")[0]
# this works as well
$ie.document.querySelector("img")
# blow it up
$ie.document.querySelectorAll("img")
# we wanna quit the process, but since we blew it up we don't really make it here
$ie.Quit()
Hope I'm not breaking any rules and this post makes sense and is relevant, thanks.
UPDATE
I tested earlier PowerShell versions. v2-v4 crash using the InternetExplorer.Application COM method. v3-4 crash using the Invoke-WebRequest method, v2 doesn't support it.
I ran into this problem, too, and posted about it on reddit. I believe the problem happens when Powershell tries to enumerate the HTML DOM NodeList object returned by querySelectorAll(). The same object is returned by childNodes() which can be enumerated by PS, so I'm guessing there's some glue code written for .ParsedHtml.childNodes but not .ParsedHtml.querySelectorAll(). The crash can be triggered by Intellisense trying to get tab-complete help for the object, too.
I found a way around it, though! Just access the native DOM methods .item() and .length directly and emit the node objects into a PowerShell array. The following code pulls the newest page of posts from /r/Powershell, gets the post list anchors via querySelectorAll() then manually enumerates them using the native DOM methods into a Powershell-native array.
$Result = Invoke-WebRequest -Uri "https://www.reddit.com/r/PowerShell/new/"
$NodeList = $Result.ParsedHtml.querySelectorAll("#siteTable div div p.title a")
$PsNodeList = #()
for ($i = 0; $i -lt $NodeList.Length; $i++) {
$PsNodeList += $NodeList.item($i)
}
$PsNodeList | ForEach-Object {
$_.InnerHtml
}
Edit .Length seems to work capitalized or lower-case. I would have expected the DOM to be case-sensitive, so either there's some things going on to help translate or I'm misunderstanding something. Also, the CSS selector is grabbing the source links (self.PowerShell mostly), but that it my CSS selector logic error, not a problem with querySelectorAll(). Note that the results of querySelectorAll() are not live, so modifying them won't modify the original DOM. And I haven't tried modifying them or using their methods yet, but clearly we can grab at the very least .InnerHtml.
Edit 2: Here is a more-generalized wrapper function:
function Get-FixedQuerySelectorAll {
param (
$HtmlWro,
$CssSelector
)
# After assignment, $NodeList will crash powershell if enumerated in any way including Intellisense-completion while coding!
$NodeList = $HtmlWro.ParsedHtml.querySelectorAll($CssSelector)
for ($i = 0; $i -lt $NodeList.length; $i++) {
Write-Output $NodeList.item($i)
}
}
$HtmlWro is an HTML Web Response Object, the output of Invoke-WebReqest. I originally tried to pass .ParsedHtml but then it would crash on assignment. Doing it this way returns the nodes in a Powershell array.
The #midnightfreddie's solution worked fine for me before, but now it throws Exception from HRESULT: 0x80020101 when calling $NodeList.item($i).
I found the following workaround:
function Invoke-QuerySelectorAll($node, [string] $selector)
{
$nodeList = $node.querySelectorAll($selector)
$nodeListType = $nodeList.GetType()
$result = #()
for ($i = 0; $i -lt $nodeList.length; $i++)
{
$result += $nodeListType.InvokeMember("item", [System.Reflection.BindingFlags]::InvokeMethod, $null, $nodeList, $i)
}
return $result
}
This one works for New-Object -ComObject InternetExplorer.Application as well.

creating GUI forms without variables

I am trying to find a way to create a form in PowerShell without using any variables unless they are temporarily or virtually assigned. I want to be able to run a command similar to this:
(New-Object System.Windows.Forms.Form).ShowDialog()
where I can enter in a code into an event that is triggered once the form is created. That event will then be responsible for creating all the objects and other events inside the form. Once the form is launched, I will not need any variables accept for the ones that are virtually assigned within the events.
This to avoid using too much system resources from assigning and endless amount of variables for each object in the form. The script that I am currently working on in PowerShell is very possibly going to be really big, and even if it is not a very large script, efficiency and clean code is always the key to writing a good program or script.
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x.ShowDialog()
Remove-Variable x
It is very possible to access these objects still with the exact same kind of access when you define each object with a variable. It cost me many hours of research and just simply attempting random commands to find out how to do this. Here is all the commands you may need to relearn if you are interested in my solution:
# create item in form:
$x.Controls.Add((New-Object System.Windows.Forms.Button))
# access the last created item in the form:
$x.Controls[-1]
# change it's name to identify it easier
$x.Controls[-1].Name = 'button1'
# access the item by it's new name:
$x.Controls['button']
# delete the item by it's name:
$x.Controls.Remove($x.Controls['button1'])
If your familiar with form creation in PowerShell then this should all make sense to you and you should be familiar with how the rest of it works. Also, another note to make for those who are interested in what I am trying to do is that any of these commands can be done within an event by replacing $x with $this. If it is inside an event of an object inside the "controls" section of the form, then you would use $this.parent.
This is exactly what I mean by having the ability to create a form with virtually no variables. The only problem I am having with this is that I am unsure how to assign an event and call the method ShowDialog() at the same time.
I found an a very interesting solution to this, however I am not sure to what the limits are to this solution and it dose not quite work in the way that I would personally like it to.
file.ps1:
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x
remove-variable x
command to execute the code:
(iex(Get-Content 'file.ps1'|out-string)).ShowDialog()

Sitecore Workflow Is Not Working

I already have an content item (item) with template A.
Template A has not set any workflow initially and I set a new workflow in template's standard value.
If I go to the content item's workflow section, there is only "Default workflow" update. The fields "Workflow" and "State" are empty even I set "Initial State" in the workflow's property, as shown below screenshot. So, workflow process is not working on that item.
However, if I create new item with the template already set workflow, I could see all filled workflow fields based on its initial workflow setting. So, workflow process is perfectly working.
I have a number of pages without workflow setting and I'm about to assign new workflow in its templates.
How can I solve this issue???????
I used Powershell Script and it looks like this.
It updates the empty field and perfectly works.
##################################################################
## 1. Set default workflow state in template's standard value ##
## 2. Before running script, must set correct Context Item ##
##################################################################
function SetWorkflow($item)
{
## Update only items assigned __Default workflow
if ($item."__Default workflow" -eq "{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}") {
$item.__Workflow = "{A5BC37E7-ED96-4C1E-8590-A26E64DB55EA}";
$item."__Workflow state" = "{190B1C84-F1BE-47ED-AA41-F42193D9C8FC}";
}
}
## Update correct workflow information.
get-item . -Language * | foreach-object { SetWorkFlow($_) }
get-childitem . -recurse -Language * | foreach-object { SetWorkFlow($_) }
## Show Updated Result
get-item . -Language * | Format-Table Id, Name, Language, __Workflow, "__Workflow state", "__Default workflow"
get-childitem . -recurse -Language * | Format-Table Id, Name, Language, __Workflow, "__Workflow state", "__Default workflow"
When you set the default workflow value on a standard values item it does not automatically and go through and add existing item versions into a specific workflow state. This is because any content that was previously published would become unpublished as it would go to the default state of the workflow, which is typically a draft state.
The next time you add a version to the item that now has workflow (either explicitly or by "editing" as a lower level user), its state should be set to draft as you'd expect.
If you need to migrate existing versions of items into workflow at particular state you will probably need to do this via code.

CQ5 / AEM5.6 Workflow: Access workflow instance properties from inside OR Split

TL;DR version:
In CQ workflows, is there a difference between what's available to the OR Split compared to the Process Step?
Is it possible to access the /history/ nodes of a workflow instance from within an OR Split?
How?!
The whole story:
I'm working on a workflow in CQ5 / AEM5.6.
In this workflow I have a custom dialog, which stores a couple of properties on the workflow instance.
The path to the property I'm having trouble with is: /workflow/instances/[this instance]/history/[workItem id]/workItem/metaData and I've called the property "reject-or-approve".
The dialog sets the property fine (via a dropdown that lets you set it to "reject" or "approve"), and I can access other properties on this node via a process step (in ecma script) using:
var actionReason;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a Action Reason that is not empty
// store that as 'actionReason' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempActionReason = previous.getWorkItem().getMetaDataMap().get('action-message');
if ((tempActionReason != '')&&(tempActionReason != null)) {
actionReason = tempActionReason;
break;
}
}
The process step is not the problem though. Where I'm having trouble is when I try to do the same thing from inside an OR Split.
When I try the same workflowSession.getHistory(workItem.getWorkflow()) in an OR Split, it throws an error saying workItem is not defined.
I've tried storing this property on the payload instead (i.e. storing it under the page's jcr:content), and in that case the property does seem to be available to the OR Split, but my problems with that are:
This reject-or-approve property is only relevant to the current workflow instance, so storing it on the page's jcr:content doesn't really make sense. jcr:content properties will persist after the workflow is closed, and will be accessible to future workflow instances. I could work around this (i.e. don't let workflows do anything based on the property unless I'm sure this instance has written to the property already), but this doesn't feel right and is probably error-prone.
For some reason, when running through the custom dialog in my workflow, only the Admin user group seems to be able to write to the jcr:content property. When I use the dialog as any other user group (which I need to do for this workflow design), the dialog looks as though it's working, but never actually writes to the jcr:content property.
So for a couple of different reasons I'd rather keep this property local to the workflow instance instead of storing it on the page's jcr:content -- however, if anyone can think of a reason why my dialog isn't setting the property on the jcr:content when I use any group other than admin, that would give me a workaround even if it's not exactly the solution I'm looking for.
Thanks in advance if anyone can help! I know this is kind of obscure, but I've been stuck on it for ages.
a couple of days ago i ran into the same issue. The issue here is that you don't have the workItem object, because you don't really have an existing workItem. Imagine the following: you are going through the workflow, you got a couple of workItems, with means, either process step, either inbox item. When you are in an or split, you don't have existing workItems, you can ensure by visiting the /workItems node of the workflow instance. Your workaround seems to be the only way to go through this "issue".
I've solved it. It's not all that elegant looking, but it seems to be a pretty solid solution.
Here's some background:
Dialogs seem to reliably let you store properties either on:
the payload's jcr:content node (which wasn't practical for me, because the payload is locked during the workflow, and doesn't let non-admins write to its jcr:content)
the workItem/metaData for the current workflow step
However, Split steps don't have access to workItem. I found a fairly un-helpful confirmation of that here: http://blogs.adobe.com/dmcmahon/2013/03/26/cq5-failure-running-script-etcworkflowscriptscaworkitem-ecma-referenceerror-workitem-is-not-defined/
So basically the issue was, the Dialog step could store the property, but the OR Split couldn't access it.
My workaround was to add a Process step straight after the Dialog in my workflow. Process steps do have access to workItem, so they can read the property set by the Dialog. I never particularly wanted to store this data on the payload's jcr:content, so I looked for another location. It turns out the workflow metaData (at the top level of the workflow instance node, rather than workItem/metaData, which is inside the /history sub-node) is accessible to both the Process step and the OR Split. So, my Process step now reads the workItem's approveReject property (set by the Dialog), and then writes it to the workflow's metaData node. Then, the OR Split reads the property from its new location, and does its magic.
The way you access the workflow metaData from the Process step and the OR Split is not consistent, but you can get there from both.
Here's some code: (complete with comments. You're welcome)
In the dialog where you choose to approve or reject, the name of the field is set to rejectApprove. There's no ./ or anything before it. This tells it to store the property on the workItem/metaData node for the current workflow step under /history/.
Straight after the dialog, a Process step runs this:
var rejectApprove;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a rejectApprove that is not empty
// store that as 'rejectApprove' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempRejectApprove = previous.getWorkItem().getMetaDataMap().get('rejectApprove');
if ((tempRejectApprove != '')&&(tempRejectApprove != null)) {
rejectApprove = tempRejectApprove;
break;
}
}
// steps up from the workflow step into the workflow metaData,
// and stores the rejectApprove property there
// (where it can be accessed by an OR Split)
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
Then after the Process step, the OR Split has the following in its tabs:
function check() {
var match = 'approve';
if (workflowData.getMetaData().get('rejectApprove') == match) {
return true;
} else {
return false;
}
}
Note: use this for the tab for the "approve" path, then copy it and replace var match = 'approve' with var match = 'reject'
So the key here is that from a Process step:
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
writes to the same property that:
workflowData.getMetaData().get('rejectApprove') reads from when you execute it in an OR Split.
To suit our business requirements, there's more to the workflow I've implemented than just this, but the method above seems to be a pretty reliable way to get values that are entered in a dialog, and access them from within an OR Split.
It seems pretty silly that the OR Split can't access the workItem directly, and I'd be interested to know if there's a less roundabout way of doing this, but for now this has solved my problem.
I really hope someone else has this same problem, and finds this useful, because it took me waaay to long to figure out, to only apply it once!