Powershell - Unable to Compare Strings Output from Sharepoint List - arrays

After extracting a list from SharePoint, I need to validate each Item against its BRTeam value. Here is the script:
cls
if((Get-PSSnapin | Where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}) -eq $null) {
Add-PSSnapin Microsoft.SharePoint.PowerShell;
}
$sourceWebUrl = "http://theoracle/WorkingHere/"
$sourceListName = "Policies & Procedures"
$spSourceWeb = Get-SPWeb $sourceWebUrl
$spSourceList = $spSourceWeb.Lists[$sourceListName]
$spSourceItems = $spSourceList.Items
$spSourceItems | ForEach-Object {
Write-Host $_['Name']
Write-Host $_['BRTeam']
}
The code works fine in terms of getting the data and writing the required items to the host.
However, if I add the following If-Statement to validate the items, I am seeing an error:
if ($_['BRTeam'].Contains('HR')) {
Write-Host $_['Name']
Write-Host $_['BRTeam']
}
I have also tried replacing the Boolean check with $x -contains 'HR' after assigning $x = $_['BRTeam'], but this returns no output (no error either). Error below:
Method invocation failed because [Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue] doesn't contain a method named 'Contains'.
At line:21 char:9
+ if ($_['BRTeam'].Contains('HR')) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Can anyone let me know what I am missing here?

I was able to resolve this by using the -Match operator instead:
$spSourceItems | ForEach-Object {
#Write-Host $_['ID']
#Write-Host $_['Workflow Started']
$x = $_['BRTeam']
if ($_['BRTeam'] -Match 'HR') {
Write-Host $_['Name']
}
}
If I am concerned that some other BRTeams may contain HR without actually being HR, I could also perform a -NotMatch against all the other departments.
E.g.:
$spSourceItems | ForEach-Object {
#Write-Host $_['ID']
#Write-Host $_['Workflow Started']
$x = $_['BRTeam']
if ($_['BRTeam'] -Notmatch 'Accounts' -And $_['BRTeam'] -Notmatch 'IT') {
Write-Host $_['Name']
}
}

Related

Powershell Format Arrays to two dimensional Arrays

i had a problem a few days ago where my script that is supposed to extract all hostnames from an AD OU and then check for the space used and free space on disks for every single host. Since this is the first time i do something with powershell i ran into many problems. The Problem that i got now is that the script cant find the hostnames listed in an array. I think i found out why it wont work because it uses the wrong hostname.
Error message i get for every hostname:
Write-Warning : Es wurde kein Positionsparameter gefunden, der das Argument "#{Name=BUCHHOLZMVZ}" akzeptiert.
In Zeile:16 Zeichen:5
+ Write-Warning "Server " $server "nicht erreichbar"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Write-Warning], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.WriteWarningCommand
When i only use the command to get all hostnames of all servers i get:
Name
----
someserver
someserver1
someserver2
(and so on...)
Here is the script:
$servers = Get-ADComputer -Filter * -SearchBase "OU=ServerOU, DC=somedomain, DC=somedomain, DC=somedomain" | Select-Object Name
$allDisks = foreach ($server in $servers)
{
try {
Get-WmiObject Win32_LogicalDisk -ComputerName $server -Filter DriveType=3 -ErrorAction Stop |
Select-Object #{'Name'='ComputerName'; 'Expression'={$server}},
DeviceID,
#{'Name'='Size'; 'Expression'={[math]::truncate($_.size / 1GB)}},
#{'Name'='Freespace'; 'Expression'={[math]::truncate($_.freespace / 1GB)}}
}
catch {
Write-Warning "Server " $server "nicht erreichbar"
Continue
}
}
$allDisks |Export-Csv C:\Servers.csv -NoTypeInformation
Use the following for your first line:
$servers = Get-ADComputer -Filter * -SearchBase "OU=ServerOU, DC=somedomain, DC=somedomain, DC=somedomain" |
Select-Object -Expand Name
Using Select-Object without -Expand or -ExpandProperty outputs an object that contains properties and values. If you only want to output values of the selected properties you must use -Expand or member access ($servers.Name).

Pulling a list of entries from Registry key and checking them for anything that is contained in an array

I am using the following code to try and pull a list of installed software on a system and check for certain entries within the list, so far I have managed to get the software list to run as desired using the following code:
$path = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
Get-ChildItem $path | Get-ItemProperty | Select-Object DisplayName
if (ItemProperty -Name -eq ('Wacom Tablet')) {
start notepad.exe
}
I would like this to be an array that references the DisplayName list, but I get the following error:
ItemProperty : Cannot find path 'C:\WINDOWS\system32\Wacom Tablet' because it
does not exist.
At C:\Users\username\Documents\Scripts\win10test.ps1:39 char:5
+ if (ItemProperty -Name -eq ('Wacom Tablet')) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\WINDOWS\system32\Wacom Tablet:String) [Get-ItemProperty], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemPropertyCommand
How could I achieve this?
ItemProperty is expanded to Get-ItemProperty, so your if condition
ItemProperty -Name -eq ('Wacom Tablet')
becomes
Get-ItemProperty -Name -eq -Path ('Wacom Tablet')
meaning that your code is trying to get a property -eq from an item Wacom Tablet in the current working directory (in your case apparently C:\WINDOWS\system32).
What you seem to want to do is something like this:
Get-ChildItem $path | Get-ItemProperty |
Where-Object { $_.DisplayName -eq 'Wacom Tablet'} |
ForEach-Object {
# do stuff
}
or like this:
$prop = Get-ChildItem $path | Get-ItemProperty |
Select-Object -Expand DisplayName
if ($prop -eq 'Wacom Tablet') {
# do stuff
}

local users and groups output to a file

I have a script that shows all the local users and their associated groups. However, I'm trying to output the results into a text file and that's where the script goes wrong, because it's not giving me the same results I'm receiving from the output window. For example, the code I have reads:
$LogFile = Test-Path C:\Users\FredAslami\Downloads\Test.txt
$LocalUsers = [ADSI]"WinNT://$env:COMPUTERNAME"
if ($LogFile) {
$LocalUsers.Children | where {$_.SchemaClassName -eq 'user'} | Foreach-Object {
$groups = $_.Groups() | Foreach-Object {
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
$_ | Select-Object #{n='UserName';e={$_.Name}},
#{n='Groups';e={$groups -join ';'}}
}
Write-Host "Got User Groups Info"
Out-File -FilePath C:\Users\FredAslami\Downloads\Test.txt `
-InputObject $LocalUsers -Append
Write-Host "Added info to text"
}
$LocalUsers.Dispose()
When I run that the text in the file will read
distinguishedName :
Path : WinNT://R68-CUSTOM-01
I have also tried using Add-Content, but that doesn't work either. It will add something like:
System.DirectoryServices.DirectoryEntry
I also, tried to debug using Write-Host after it retrieves the local users and group info and another Write-Host after it writes the results into the text file and noticed that it's writing the results before it gathered all the info. So I tried using the Start-Sleep, and that didnt seem to work.
On the second line you have $LocalUsers = [ADSI]"WinNT://$env:COMPUTERNAME". You never assigned it a different value, so that's what you're seeing as your output.
I would recommend piping your Select-Object statement to Export-Csv. Much easier and cleaner.
You get different results in screen and file output, because you're doing different things with your data. The pipeline starting with $localUsers.Children builds a list of the user objects and their group memberships and echoes that to the screen, but you don't do anything else with that data. Instead you're writing the unmodified variable $localUsers to the output file.
If you want tabular data to go both to the console and a file, I'd suggest using Write-Host for the console output, and Export-Csv for the file output:
$LocalUsers.Children | where {$_.SchemaClassName -eq 'user'} | Foreach-Object {
$groups = $_.Groups() | Foreach-Object {
$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
}
$o = New-Object -Type PSObject -Property #{
'UserName' = $_.Name
'Groups' = $groups -join ';'
}
Write-Host $o
$o
} | Export-Csv 'C:\Users\FredAslami\Downloads\Test.txt' -NoType
If you want the output to go to the success output stream instead of the console, you could capture the result in a variable and output that in two different ways:
$users = $LocalUsers.Children | where {
$_.SchemaClassName -eq 'user'
} | Foreach-Object {
$groups = $_.Groups() | Foreach-Object {
$_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null)
}
New-Object -Type PSObject -Property #{
'UserName' = $_.Name
'Groups' = $groups -join ';'
}
}
$users
$users | Export-Csv 'C:\Users\FredAslami\Downloads\Test.txt' -NoType

PowerShell: Set-Content having issues with "file already in use"

I'm working on a PowerShell script that finds all the files with PATTERN within a given DIRECTORY, prints out the relevant lines of the document with the PATTERN highlighted, and then replaces the PATTERN with a provided REPLACE word, then saves the file back. So it actually edits the file.
Except I can't get it to alter the file, because Windows complains about the file already being open. I tried several methods to solve this, but keep running into the issue. Perhaps someone can help:
param(
[string] $pattern = ""
,[string] $replace = ""
,[string] $directory ="."
,[switch] $recurse = $false
,[switch] $caseSensitive = $false)
if($pattern -eq $null -or $pattern -eq "")
{
Write-Error "Please provide a search pattern." ; return
}
if($directory -eq $null -or $directory -eq "")
{
Write-Error "Please provide a directory." ; return
}
if($replace -eq $null -or $replace -eq "")
{
Write-Error "Please provide a string to replace." ; return
}
$regexPattern = $pattern
if($caseSensitive -eq $false) { $regexPattern = "(?i)$regexPattern" }
$regex = New-Object System.Text.RegularExpressions.Regex $regexPattern
function Write-HostAndHighlightPattern([string] $inputText)
{
$index = 0
$length = $inputText.Length
while($index -lt $length)
{
$match = $regex.Match($inputText, $index)
if($match.Success -and $match.Length -gt 0)
{
Write-Host $inputText.SubString($index, $match.Index) -nonewline
Write-Host $match.Value.ToString() -ForegroundColor Red -nonewline
$index = $match.Index + $match.Length
}
else
{
Write-Host $inputText.SubString($index) -nonewline
$index = $inputText.Length
}
}
}
Get-ChildItem $directory -recurse:$recurse |
Select-String -caseSensitive:$caseSensitive -pattern:$pattern |
foreach {
$file = ($directory + $_.FileName)
Write-Host "$($_.FileName)($($_.LineNumber)): " -nonewline
Write-HostAndHighlightPattern $_.Line
%{ Set-Content $file ((Get-Content $file) -replace ([Regex]::Escape("[$pattern]")),"[$replace]")}
Write-Host "`n"
Write-Host "Processed: $($file)"
}
The issue is located within the final block of code, right at the Get-ChildItem call. Of course, some of the code in that block is now a bit mangled due to me trying to fix the problem then stopping, but keep in mind the intent of that part of the script. I want to get the content, replace the words, then save the altered text back to the file I got it from.
Any help at all would be greatly appreciated.
Removed my previous answer, replacing it with this:
Get-ChildItem $directory -recurse:$recurse
foreach {
$file = ($directory + $_.FileName)
(Get-Content $file) | Foreach-object {
$_ -replace ([Regex]::Escape("[$pattern]")),"[$replace]")
} | Set-Content $file
}
Note:
The parentheses around Get-Content to ensure the file is slurped in one go (and therefore closed).
The piping to subsequent commands rather than inlining.
Some of your commands have been removed to ensure it's a simple test.
Just a suggestion but you might try looking at the documentation for the parameters code block. There is a more efficient way to ensure that a parameter is entered if you require it and to throw an error message if the user doesn't.
About_throw: http://technet.microsoft.com/en-us/library/dd819510.aspx
About_functions_advanced_parameters: http://technet.microsoft.com/en-us/library/dd347600.aspx
And then about using Write-Host all the time: http://powershell.com/cs/blogs/donjones/archive/2012/04/06/2012-scripting-games-commentary-stop-using-write-host.aspx
Alright, I finally sat down and just typed everything sequentially in PowerShell, then used that to make my script.
It was actually really simple;
$items = Get-ChildItem $directory -recurse:$recurse
$items |
foreach {
$file = $_.FullName
$content = get-content $file
$newContent = $content -replace $pattern, $replace
Set-Content $file $newcontent
}
Thanks for all your help guys.

How to remove item from an array in PowerShell?

I'm using Powershell 1.0 to remove an item from an Array. Here's my script:
param (
[string]$backupDir = $(throw "Please supply the directory to housekeep"),
[int]$maxAge = 30,
[switch]$NoRecurse,
[switch]$KeepDirectories
)
$days = $maxAge * -1
# do not delete directories with these values in the path
$exclusionList = Get-Content HousekeepBackupsExclusions.txt
if ($NoRecurse)
{
$filesToDelete = Get-ChildItem $backupDir | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
else
{
$filesToDelete = Get-ChildItem $backupDir -Recurse | where-object {$_.PsIsContainer -ne $true -and $_.LastWriteTime -lt $(Get-Date).AddDays($days)}
}
foreach ($file in $filesToDelete)
{
# remove the file from the deleted list if it's an exclusion
foreach ($exclusion in $exclusionList)
{
"Testing to see if $exclusion is in " + $file.FullName
if ($file.FullName.Contains($exclusion)) {$filesToDelete.Remove($file); "FOUND ONE!"}
}
}
I realize that Get-ChildItem in powershell returns a System.Array type. I therefore get this error when trying to use the Remove method:
Method invocation failed because [System.Object[]] doesn't contain a method named 'Remove'.
What I'd like to do is convert $filesToDelete to an ArrayList and then remove items using ArrayList.Remove. Is this a good idea or should I directly manipulate $filesToDelete as a System.Array in some way?
Thanks
The best way to do this is to use Where-Object to perform the filtering and use the returned array.
You can also use #splat to pass multiple parameters to a command (new in V2). If you cannot upgrade (and you should if at all possible, then just collect the output from Get-ChildItems (only repeating that one CmdLet) and do all the filtering in common code).
The working part of your script becomes:
$moreArgs = #{}
if (-not $NoRecurse) {
$moreArgs["Recurse"] = $true
}
$filesToDelete = Get-ChildItem $BackupDir #moreArgs |
where-object {-not $_.PsIsContainer -and
$_.LastWriteTime -lt $(Get-Date).AddDays($days) -and
-not $_.FullName.Contains($exclusion)}
In PSH arrays are immutable, you cannot modify them, but it very easy to create a new one (operators like += on arrays actually create a new array and return that).
I agree with Richard, that Where-Object should be used here. However, it's harder to read.
What I would propose:
# get $filesToDelete and #exclusionList. In V2 use splatting as proposed by Richard.
$res = $filesToDelete | % {
$file = $_
$isExcluded = ($exclusionList | % { $file.FullName.Contains($_) } )
if (!$isExcluded) {
$file
}
}
#the files are in $res
Also note that generally it is not possible to iterate over a collection and change it. You would get an exception.
$a = New-Object System.Collections.ArrayList
$a.AddRange((1,2,3))
foreach($item in $a) { $a.Add($item*$item) }
An error occurred while enumerating through a collection:
At line:1 char:8
+ foreach <<<< ($item in $a) { $a.Add($item*$item) }
+ CategoryInfo : InvalidOperation: (System.Collecti...numeratorSimple:ArrayListEnumeratorSimple) [], RuntimeException
+ FullyQualifiedErrorId : BadEnumeration
This is ancient. But, I wrote these a while ago to add and remove from powershell lists using recursion. It leverages the ability of powershell to do multiple assignment . That is, you can do $a,$b,$c=#('a','b','c') to assign a b and c to their variables. Doing $a,$b=#('a','b','c') assigns 'a' to $a and #('b','c') to $b.
First is by item value. It'll remove the first occurrence.
function Remove-ItemFromList ($Item,[array]$List(throw"the item $item was not in the list"),[array]$chckd_list=#())
{
if ($list.length -lt 1 ) { throw "the item $item was not in the list" }
$check_item,$temp_list=$list
if ($check_item -eq $item )
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-ItemFromList -item $item -chckd_list $chckd_list -list $temp_list )
}
}
This one removes by index. You can probably mess it up good by passing a value to count in the initial call.
function Remove-IndexFromList ([int]$Index,[array]$List,[array]$chckd_list=#(),[int]$count=0)
{
if (($list.length+$count-1) -lt $index )
{ throw "the index is out of range" }
$check_item,$temp_list=$list
if ($count -eq $index)
{
$chckd_list+=$temp_list
return $chckd_list
}
else
{
$chckd_list+=$check_item
return (Remove-IndexFromList -count ($count + 1) -index $index -chckd_list $chckd_list -list $temp_list )
}
}
This is a very old question, but the problem is still valid, but none of the answers fit my scenario, so I will suggest another solution.
I my case, I read in an xml configuration file and I want to remove an element from an array.
[xml]$content = get-content $file
$element = $content.PathToArray | Where-Object {$_.name -eq "ElementToRemove" }
$element.ParentNode.RemoveChild($element)
This is very simple and gets the job done.

Resources