Exporting System.Object[] to string in PowerShell - string

I created a script that basically iterates through all lists in a site and its subsites and lists the permissions for each document or item.
The script works, however, when being written to the CSV file the permissions (Member + Role) string gets written as System.Object[] instead of a proper string in the CSV output (e.g. Member: user123 Role: Full Control).
Sample output:
"Common Hover Panel Actions", "https://#######.####.###.##", "https://######.######.#####.####", "System.Object[]", " Ministry for Finance", "Master Page Gallery", "12/22/2015 8:34:39 AM", "_catalogs/masterpage/Display Templates/Search/Item_CommonHoverPanel_Actions.js", "12/22/2015 8:34:39 AM", "6.2763671875"
The below is the script:
Add-PSSnapin Microsoft.SharePoint.PowerShell
function Get-DocInventory([string]$siteUrl) {
$site = New-Object Microsoft.SharePoint.SPSite $siteUrl
$i = 0;
foreach ($web in $site.AllWebs) {
$count = $site.AllWebs.Count
foreach ($list in $web.Lists) {
if ($list.BaseType -ne “DocumentLibrary”)
{
continue
}
foreach ($item in $list.Items) {
$data = #{
"Web Application" = $web.ToString()
"Site" = $site.Url
"Web" = $web.Url
"list" = $list.Title
"Item URL" = $item.Url
"Item Title" = $item["Title"]
"Item Created" = $item["Created"]
"Item Modified" = $item["Modified"]
"File Size" = $item.File.Length/1KB
"Permissions" = foreach($roleAssignment in $item.RoleAssignments)
{
$Permission = ""
foreach($roleDefinition in $roleAssignment.RoleDefinitionBindings)
{
$Permission = $(foreach($roleAssignment in $item.RoleAssignments){$roleAssignment}
{
"Member: " + $roleAssignment.Member.LoginName + " Role: " + $roleDefinition.Name + " "
} -join ',')
$Permission
Write-Progress -activity "Working" -status "Checked Sub-Site $i of $count" -percentComplete (($i / $count)/100)
}
}
}
New-Object PSObject -Property $data
}
}
$web.Dispose();
}
$i++
$site.Dispose()
}
<# Get-DocInventory "https://#####.####.###.##/" | Out-GridView #>
Get-DocInventory "https://#####.####.###.##/" | Export-Csv -NoTypeInformation -Path C:\Users\livob002-lap\Desktop\Permissions-FinanceIntranet.csv
Is it possible to export the System.Object[] for the username as string in the CSV? The names appear as they should in the GridView.

All I had to do was:
$mystring = $myobject | Out-String

There are a few ways to avoid it.
Using –Join
[pscustomobject]#{
Value = (#(1,3,5,6) -join ',')
} | Export-Csv -notype output.csv
Out-String and Trim()
[pscustomobject]#{
Value = (#(1,3,5,6) | Out-String).Trim()
} | Export-Csv -notype output.csv
Reference: Avoiding System.Object[] (or Similar Output) when using Export-Csv

The foreach statement returns an array, so the line
"Permissions" = foreach($roleAssignment in $item.RoleAssignments)
assigns a System.Array to the key "Permissions". The ToString() method for a System.Array returns System.Object[].
You can "convert" a string array to a string by using the -join operator. The following could work.
"Permissions" = foreach($roleAssignment in $item.RoleAssignments)
{
...
} -join ', '

Related

Powershell script and Excel

I had coded a powershell executable in a evaluation windows server, where it worked fine. That is because it had Office (Excel) installed, hence it was possible to run $ExcelObj = New-Object -comobject Excel.Application. However, when moving on to the production server, my systems admin stressed that Office installation is strictly prohibited in the production server, making it not possible to execute the above command, and hence the remaining commands. I am not sure how to continue/change my code from this point onwards. I did search up and found this. But Its structure and method of writing to excel file is too "example", and am not able to figure out how to modify it to my preference. I really need this to work out.
Script.ps1:
#Exception for error hiding during debug
$ErrorActionPreference = 'SilentlyContinue'
#Variables
$PathFileCSV = "C:\Users\Administrator\Desktop\FinalPrototype.csv" #for actual
$PathFileXLSX = "C:\Users\Administrator\Desktop\FinalPrototype.xlsx" #for actual
#Function for file deletion (xlsx)
if ((Test-Path $PathFileXLSX)) {
Remove-Item $PathFileXLSX -Force
}
#Code to stop any running instance of excel, to prevent additional files from being created
# Stop-Process -processname excel -Force
#Function from Github (src:https://github.com/gangstanthony/PowerShell)
function Save-CSVasExcel {
param (
[string]$CSVFile = $(Throw 'No file provided.')
)
BEGIN {
function Resolve-FullPath ([string]$Path) {
if ( -not ([System.IO.Path]::IsPathRooted($Path)) ) {
# $Path = Join-Path (Get-Location) $Path
$Path = "$PWD\$Path"
}
[IO.Path]::GetFullPath($Path)
}
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
$CSVFile = Resolve-FullPath $CSVFile
$xl = New-Object -ComObject Excel.Application
}
PROCESS {
$wb = $xl.workbooks.open($CSVFile)
$xlOut = $CSVFile -replace '\.csv$', '.xlsx'
# can comment out this part if you don't care to have the columns autosized
# $ws = $wb.Worksheets.Item(1)
# $range = $ws.UsedRange
# [void]$range.EntireColumn.Autofit()
$num = 1
$dir = Split-Path $xlOut
$base = $(Split-Path $xlOut -Leaf) -replace '\.xlsx$'
$nextname = $xlOut
while (Test-Path $nextname) {
$nextname = Join-Path $dir $($base + "-$num" + '.xlsx')
$num++
}
$wb.SaveAs($nextname, 51)
}
END {
$xl.Quit()
$null = $ws, $wb, $xl | ForEach-Object { Release-Ref $_ }
del $CSVFile
}
}
#Function to create the CSV File
Get-ADUser -Filter * -Properties * -SearchBase "DC=devops,DC=company1" | select-object displayName, mail, #{n = 'OU'; e = { (($_.DistinguishedName -replace '^.*?,(?=[A-Z]{2}=)' -split ',')[0]).substring(3) } } | Export-CSV -Path $PathFileCSV -NoTypeInformation
#select-object #{name = "MemberOf"; expression = { ($_.memberof | ForEach-Object { (Get-ADObject $_).Name }) -join " | " } } --- for user roles
#select-object sAMAccountName --- for username
#Function to convert created CSV to XLS (With formatted column)
Save-CSVasExcel $PathFileCSV
#Function for file deletion (csv)
#if ((Test-Path $PathFileCSV)) {
# Remove-Item $PathFileCSV -Force
#}
#Beautification
$ExcelObj = New-Object -comobject Excel.Application
$ExcelWorkBook = $ExcelObj.Workbooks.Open($PathFileXLSX)
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Item("FinalPrototype")
#$ExcelWorkSheet.Cells.Font.Name = "Comfortaa"
$ExcelWorkSheet.Cells.Font.Size = 11
#Function for finding user groups & more
$usersAMAccName = Get-ADUser -Filter * -Properties * -SearchBase "DC=devops,DC=company1" | select-object sAMAccountName
$hash.Clear()
$data = #('Name', 'Email', 'Organizational Unit')
$data += #(Get-ADGroup -filter * -searchbase "OU=DevOps User Groups,OU=C1,DC=devops,DC=company1" | Select-Object -expandproperty name )
foreach ($name in $usersAMAccName) {
$hash += [ordered]#{ $name.sAMAccountName = (Get-ADPrincipalGroupMembership $name.sAMAccountName | select-object name).name }
}
#Header-Row-only Beautification
$Rows = $data.Count
for ($i = 1; $i -le $Rows; $i++) {
$ExcelWorkSheet.Cells.Item(1, $i).Font.Size = 16
$ExcelWorkSheet.Cells.Item(1, $i) = $data[$i - 1]
$ExcelWorkSheet.Cells.Item(1, $i).EntireRow.Interior.ColorIndex = 6
}
#Insertion profile-related data
$range = $ExcelWorkSheet.UsedRange
$rangeROW = $ExcelWorkSheet.UsedRange.Rows.Count
$rangeCOL = $ExcelWorkSheet.UsedRange.Columns.Count
for ($i = 2; $i -le $rangeROW; $i++) {
for ($j = 4; $j -le $rangeCOL; $j++) {
if ($hash.($usersAMAccName[$i - 2] | select-object -expandproperty sAMAccountName) -contains $ExcelWorkSheet.Cells.Item(1, $j).value2()) {
$ExcelWorkSheet.Cells.Item($i, $j).Interior.ColorIndex = 4
$ExcelWorkSheet.Cells.Item($i, $j) = "Assigned"
}
else {
$ExcelWorkSheet.Cells.Item($i, $j).Interior.ColorIndex = 3
$ExcelWorkSheet.Cells.Item($i, $j) = "Not Assigned"
}
}
}
#Content-usedRange-only Beautification
$range.EntireColumn.Autofit()
#Non-Human user omission
for ($i = 1; $i -le $rangeROW; $i++) {
If ([string]::IsNullOrEmpty($ExcelWorkSheet.Cells.Item($i, 1).value2)) {
$ExcelWorkSheet.Cells.Item($i, 1).EntireRow.hidden = $true
}
}
#Beautification
$range.WrapText = "True"
$range.HorizontalAlignment = -4108
#Saving the final form of excel
$ExcelObj.DisplayAlerts = $FALSE
$ExcelWorkBook.Save()
$ExcelWorkBook.close($true)
#User Ref (Debug)
Clear-Host
Write-Host 'Debug: Reading' $PathFileXLSX 'now...'
$data = Import-Excel $PathFileXLSX
$data
Final .xlsx file when ran in evaluation server (with excel):

How to retrieve the user that did the last modification in the site

I must retrieve the name of the user that did the last modification in a given web, using a Power Shell script.
I know that retrieving the last item modification date is straightforward, but how to retrieve the user that did such modification?
Retrieving the last modified item in the web would be fine too, since I would then pick the value of the "Modified By" field.
So... just to confirm... You want to check all the items in all the lists and libs in some web and get the last modified item from every list and get the user that modified it, correct? :)
if that is the case this kind of PS script should do the trick
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null)
{
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}
try
{
$siteUrl="[URL]";
$web = Get-SPWeb $siteUrl;
$spQuery = New-Object Microsoft.SharePoint.SPQuery;
$spQuery.ViewAttributes = "Scope='Recursive'";
$spQuery.Query = ""
$spQuery.RowLimit = 1;
$lastDate = $null;
$lastUser = $null;
foreach($list in $web.Lists)
{
Write-Host 'checking list -' $list.Title;
$items = $list.GetItems($spQuery);
if($items.Count -gt 0)
{
if($lastDate -eq $null)
{
$lastDate = $items[0]['Modified'];
$lastUser = $items[0]['Editor'];
}
else
{
if((get-date $items[0]['Modified']) -gt (get-date $lastDate))
{
$lastDate = $items[0]['Modified'];
$lastUser = $items[0]['Editor'];
}
}
}
}
Write-Host 'last user that modfied some item in this web was - '$lastUser ' - ' $lastDate;
}
catch
{
Write-Host $_.Exception.Message;
}
I hope it helps :).
You could compare the web object's LastItemModifiedDate property to the property with the same name on each list on the site. There should only be one list with the identical LastItemModifiedDate.
$list = $web.lists | where-object {$_.LastItemModifiedDate -eq $web.LastItemModifiedDate }
You can then just query for the most recently modified item in that list and see who edited it.
$query = new-object Microsoft.SharePoint.SPQuery
$query.Query = "<OrderBy><FieldRef Name='Modified' Ascending='FALSE' /></OrderBy>"
$query.RowLimit = 1
$items = $list.GetItems($query)
$item = $items[0]
A complete script might look something like this (replacing $webUrl with the desired web URL):
$web = get-spweb $webUrl
$list = $web.lists | where-object {$_.LastItemModifiedDate -eq $web.LastItemModifiedDate }
$query = new-object Microsoft.SharePoint.SPQuery
$query.Query = "<OrderBy><FieldRef Name='Modified' Ascending='FALSE' /></OrderBy>"
$query.RowLimit = 1
$items = $list.GetItems($query)
$item = $items[0]
write-host "$($web.AllUsers.GetById([int32]$item["Editor"].split(";#")[0]).DisplayName) modified item with ID $($item.ID) in $($list.Title) on $($item["Modified"])"
$web.dispose()
Below is an improved and tested version:
$web = Get-SPWeb $url
$webLastItemModifiedDate = $web.LastItemModifiedDate
$list = $web.lists | where-object {$_.LastItemModifiedDate -eq $webLastItemModifiedDate }
$formattedLastItemModifiedDate = $webLastItemModifiedDate.toString("yyyy-MM-ddTHH:mm:ssZ")
$query = new-object Microsoft.SharePoint.SPQuery
$query.Query = "<Where><Eq><FieldRef Name='Modified' /><Value Type='DateTime'>" + $formattedLastItemModifiedDate + "</Value></Eq></Where>"
$query.RowLimit = 1
$items = $list.GetItems($query)
$item = $items[0]
$webLastModifier = $web.AllUsers.GetById([int32]$item["Editor"].split(";#")[0]).DisplayName

speed up in DC's scanning

I'm building a script which will go to each DC's and take the value from modifyTimeStampe values and then on take the maximum value as the following code, at the moment the code is too slow because now it goes to each domain and put the value to the array, then get the max value from the array out. I would like to speed it up. I was thinking to use multiple threads, but still looking for a better idea to implement.
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 100
$Searcher.SearchScope = "subtree"
$Searcher.Filter = "(&(objectCategory=$objectCategory)(objectClass=$objectClass))"
$Searcher.PropertiesToLoad.Add("distinguishedName")|Out-Null
$Searcher.PropertiesToLoad.Add("modifyTimeStamp")|Out-Null
Function modiScan{
forEach ($users In $userObjects)
{
$DN = $users.Properties.Item("distinguishedName")[0]
$dnarr.add($DN)|Out-Null
}
#$dnarr
foreach($dnn in $dnarr){
$error = $false
$lastmd = New-Object System.Collections.ArrayList
ForEach ($DC In $Domain.DomainControllers){
$Server = $DC.Name
$Base = "LDAP://$Server/"+$dnn
$Searcher.SearchRoot = $Base
try{
$Results2 = $Searcher.FindAll()
ForEach ($Result2 In $Results2)
{
$DN2 = $Result2.Properties.Item("distinguishedName")[0]
if($DN2 -eq $dnn){
$modi = $Result2.Properties.Item("modifyTimeStamp")[0]
if($modi){
$lastmd.Add($modi)|Out-Null
}
}
}
}
catch{
$error = $true
}
}
if($error -eq $true){
$lastModi = "None-set"
$global:noneModi++
}
else{
$lastModi = ($lastmd |measure -max).maximum
if($lastModi -ne $null){
$lastModi = $lastModi.ToString("yyyy/MM/dd")
if($lastModi.split("/")[0] -eq 2015){
$global:modi2015++
}
elseif($lastModi.split("/")[0] -eq 2016){
$global:modi2016++
}
elseif($lastModi.split("/")[0] -eq 2017){
$global:modi2017++
}
else{
$global:otherModi++
}
}
else{
$lastModi = "N/A"
$global:noneModi++
}
}
#$lastModi
$obj = New-Object -TypeName psobject
$obj | Add-Member -MemberType NoteProperty -Name "modi" -Value $lastModi
$obj | Export-Csv -Path "$outFileModi" -NoTypeInformation -append -Delimiter $Delimiter
}
}
modiScan

PowerShell - How to check a string to see if it contains another string with wildcard?

I want to go through a list of files and check if each filename match any of the string in a list. This is what I have so far, but it is not finding any match. What am I doing wrong?
$files = $("MyApp.Tests.dll","MyApp.Tests.pdb","MyApp.dll")
$excludeTypes = $("*.Tests.dll","*.Tests.pdb")
foreach ($file in $files)
{
$containsString = foreach ($type in $ExcludeTypes) { $file | %($_ -match '$type') }
if($containsString -contains $true)
{
Write-Host "$file contains string."
}
else
{
Write-Host "$file does NOT contains string."
}
}
With wildcards you want to use -like operator instead of -match because the latter requires a regular expression. Example:
$files = #("MyApp.Tests.dll","MyApp.Tests.pdb","MyApp.dll")
$excludeTypes = #("*.Tests.dll","*.Tests.pdb")
foreach ($file in $files) {
foreach ($type in $excludeTypes) {
if ($file -like $type) {
Write-Host ("Match found: {0} matches {1}" -f $file, $type)
}
}
}

How to pipe the result of a foreach loop into a csv file with PowerShell

I am having the following syntax problem, and I am still unsure if this is possible or not.
An empty pipe element is not allowed.
The script is
$web = get-spweb https://xx.com/sites/billing
$list = $web.Lists["Bill Cycles"]
foreach($wf in $list.WorkflowAssociations)
{
if ($wf.Name -like "*2013*")
{
foreach($listitem in $list.Items)
{
foreach($Workflow in $listitem.Workflows)
{
if($wf.InternalStatus -ne "Completed")
{
if($Workflow.AssociationId -eq $wf.Id)
{
New-Object psobject -Property #{
"InternalStatus" = $wf.InternalStatus
"WFName" = $wf.Name
"ListItemName" = $listitem.Name
"Url" = $listitem.Url
"Days" = ((Get-Date) - $Workflow.Created).Days
}
}
}
}
}
}
} | Select-Object InternalStatus, WFName, ListItemName, Url, Days | Export-CSV $output -Delimiter ',' -NoTypeInformation
You can make a foreach loop output to the pipeiline by using a sub-expression:
$web = get-spweb https://xx.com/sites/billing
$list = $web.Lists["Bill Cycles"]
$(foreach($wf in $list.WorkflowAssociations)
{
if ($wf.Name -like "*2013*")
{
foreach($listitem in $list.Items)
{
foreach($Workflow in $listitem.Workflows)
{
if($wf.InternalStatus -ne "Completed")
{
if($Workflow.AssociationId -eq $wf.Id)
{
New-Object psobject -Property #{
"InternalStatus" = $wf.InternalStatus
"WFName" = $wf.Name
"ListItemName" = $listitem.Name
"Url" = $listitem.Url
"Days" = ((Get-Date) - $Workflow.Created).Days
}
}
}
}
}
}
}) | Select-Object InternalStatus, WFName, ListItemName, Url, Days | Export-CSV $output -Delimiter ',' -NoTypeInformation
You can also use a scriptblock invocation (wrap the loop in &{} instead of $() )
A foreach loop doesn't output to the pipeline, so it's likely Export-CSV hasn't got any input.
Try wrapping the iteration of WorkflowAssociations in parenthesis.

Resources