Creating profile array with powershell - arrays

I'm still learning the basics of powershell, but I have come across an issue I can't seem to resolve as I just don't have enough knowledge.
I'm creating a script to do user profile migrations and I want the code to gather profiles from the local machine, convert the SID back to usernames and list them in a drop down box (which works), but only lists one user. I have this:
$Profiles = gwmi -Class Win32_UserProfile -Filter ("Special = False")
$output = foreach ($Profile in $Profiles)
{
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
Write-Host $objuser.value
$array = #($objuser)
Any ideas?
TIA!

It appears that you're overwriting the contents of $array on each iteration of your foreach loop. Instead, append to it.
foreach ($Profile in $Profiles)
{
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
Write-Host $objuser.value
$array += #($objuser)
}
But I may be wrong. You've pasted only part of your script here (the braces for foreach aren't balanced, and we have no insight into how that drop-down list is being populated), so there may be something later on that's messing you up.

See comments in code.
$Profiles = gwmi -Class Win32_UserProfile -Filter ("Special = False")
#You never output anything in your foreach-loop, so $output will be empty.. Removed Write-Host later in code to fix this
$output = foreach ($Profile in $Profiles) {
try
{
$objSID = New-Object System.Security.Principal.SecurityIdentifier($profile.sid)
$objuser = $objsid.Translate([System.Security.Principal.NTAccount])
$objusername = $objuser.value
}
catch
{
$objusername = $profile.sid
}
#You've already saved "objuser.value to a variable... use it.. :) Also, You're catching returned objects with $output = foreach, so I'd suggest outputing the usernames and not just write them to the console. Replace `Write-Host $objuser.value` with `$objusername`
$objusername
#You never closed your foreachloop. Added }
}
#Output collected usernames
$output
#This will always overwrite $array with a new array containing one user, objuser, only. Removed
#$array = #($objuser)

Related

values from a foreach loop in a function into an array

I have a function that replaces PackageID in a SCCM task sequence, I would like to capture all those package IDs into a variable, so I would be able to create a report based on that.
The problem is that I already have a foreach loop doing the work, and I can't figure out how to not overwrite the values.
$Driver.PackageID comes from a foreach loop based on $Drivers, which contains
If I run the code I get this as I have Write-Output defined:
Updated code:
function Set-Drivers{
foreach ($Driver in $Drivers) {
Write-Output "Driver Name: $($Driver.Name)"
Write-Output "DriverPackageID: $($Driver.PackageID)"
}
}
$array = #()
$array = Set-Drivers
$hash = [ordered]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
$array += New-Object -Typename PSObject -Property $hash
Can someone explain, why I only get the first result in my $array? I can see the values are being overwritten if I run it in debug mode.
Your code is not iterating over the results, but instead only using one of them. This what you intended.
$array = $drivers | foreach {
[ordered]#{
DriverName = $_.Name
DriverID = $_.PackageID
}
}
Your function doesn't return anything. It only writes lines to the console. Then after the function is finished, you create a single object and add that to your array.
Try something like
function Set-Drivers{
$result = foreach ($Driver in $Drivers) {
[PsCustomObject]#{
'DriverName' = $Driver.Name
'DriverID' = $Driver.PackageID
}
}
# output the result
# the comma wraps the result in a single element array, even if it has only one element.
# PowerShell 'flattens' that upon return from the function, leaving the actual resulting array.
,$result
}
$array = Set-Drivers
# show what you've got
$array

Using Invoke-WebRequest on an array in PowerShell

I'm trying write a script that will grab the fortune 100 URLs from here, put those into an array, and then write a runspace that uses Invoke-WebRequest to get the content of those URLs and writes that content to a file. This is the code that I have so far:
#Importing Modules
Import-Module PoshRSJob
#variable declaration
$page = Invoke-WebRequest https://www.zyxware.com/articles/4344/list-of-fortune-500-companies-and-their-websites
$links = $page.Links
$tables = #($page.ParsedHtml.GetElementsByTagName("TABLE"))
$tableRows = $tables[0].Rows
#loops through the table to get only the top 100 urls.
$urlArray = #()
foreach ($tablerow in $tablerows) {
$urlArray += New-Object PSObject -Property #{'URLName' = $tablerow.InnerHTML.Split('"')[1]}
#Write-Host ($tablerow.innerHTML).Split('"')[1]
$i++
if ($i -eq 101) {break}
}
#Number of Runspaces to use
#$RunspaceThreads = 1
#Declaring Variables
$ParamList = #($urlArray)
$webRequest = #()
$urlArray | start-rsjob -ScriptBlock {
#$webRequest = (Invoke-WebRequest $using:ParamList)
#Invoke-WebRequest $urlArray
#Invoke-WebRequest {$urlArray}
#Get-Content $urlArray
}
The problem that I'm running into right now is that I can't get Invoke-WebRequest or Get-Content to give me the contents of the URLs that are actually contained in the array. You can see that in the scriptblock, I commented out some lines that didn't work.
My question is: using a runspace, what do I need to do to pull the data from all the URLs in the array using Get-Content, and then write that to a file?
You can adjust your current query to get the first 100 company names. This skips the empty company at the front. Consider using [PSCustomObject] #{ URLName = $url } which replaces the legacy New-Object PSObject.
$urlArray = #()
$i = 0
foreach ($tablerow in $tablerows) {
$url = $tablerow.InnerHTML.Split('"')[1]
if ($url) {
# Only add an object when the url exists
$urlArray += [PSCustomObject] #{ URLName = $url }
$i++
if ($i -eq 100) {break}
}
}
To run the requests in parallel use Start-RSJob with a script block. Invoke-Webrequest is then run in parallel. Note that in this example $_ refers to the current array element that is piped which consists of an object with a URLName property, but you need to be a little careful what variables you use inside the scriptblock because they might not be resovled they way you expect them to be.
# Run the webrequests in parallel
# $_ refers to a PSCustomObject with the #{ URLName = $url } property
$requests = ($urlArray | start-rsjob -ScriptBlock { Invoke-WebRequest -Uri $_.URLName })
You can then wait for all the jobs to complete and do some post processing of the results.
Here only the length of the website contents are written because the pages themself are lengthy.
# Get the results
# $_.Content.Length gets the length of the content to not spam the output with garbage
$result = Get-RSjob | Receive-RSJob | ForEach { $_.Content.Length }
Write-Host $result

Convert a SharePoint Online list into JSON using arrays

I'm trying to convert a set of SharePoint list items (and associated data) into a JSON object. To do this I'm trying to create a multi-dimensional array and then iterate over my SharePoint objects to populate it.
This is the relevant code so far:
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = #()
$listArray["DisplayTitle"] = #()
$listArray["Description"] = #()
$listArray["Setting"] = #()
$listArray["HealthAreas"] = #()
$listArray["ResourceType"] = #()
$listArray["ExternalURL"] = #()
$listArray["Active"] = #()
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray["DisplayTitle"].Add($item["Title"])
$listArray["Description"].Add($item["File Description"])
$listArray["Setting"].Add($item["Setting"])
$listArray["HealthAreas"].Add($item["Health_x0020_Area"])
$listArray["ResourceType"].Add($item["Resource_x0020_Type"])
$listArray["ExternalURL"].Add($item["External_x0020_file_x0020_path"])
$listArray["Active"].Add($item["Currently_x0020_active_x003f_"])
}
Write-Host "############################"
Write-Host $listArray | ConvertTo-Json
I know there's a gap in my thinking here (maybe I need a hashtable) but just can't see it. The error I'm receiving is:
You cannot call a method on a null-valued expression.
However I can't see where my null variable may be originating from as I've confirmed each item in the loop does contain data (by writing to console).
The error that you receive is not related to SharePoint but to PowerShell. You created the PowerShell array and tried to access its elements like it was associative array/hashtable.
Please try this code (I've tested it with my own list with different column names and it works fine):
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = New-Object System.Collections.Generic.List[System.Object]
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray.Add([hashtable]#{
DisplayTitle=$item["Title"];
Description= $item["File Description"];
Setting= $item["Setting"];
HealthAreas= $item["Health_x0020_Area"];
ResourceType= $item["Resource_x0020_Type"];
ExternalURL= $item["External_x0020_file_x0020_path"];
Active= $item["Currently_x0020_active_x003f_"];
}
)
}
Write-Host "############################"
$json = $listArray | ConvertTo-Json
Write-Host $json

Powershell Write-Host showing only dataTable name instead of data

I'm trying to write a Powershell script that executes a SQL query contained in a .sql file
Function RunSQLScript ($connstring, $filePath)
{
$query = get-content $filePath;
$DTSet = New-Object System.Data.DataSet;
$Conn=New-Object System.Data.SQLClient.SQLConnection $connstring;
$Conn.Open();
try
{
$DataCmd = New-Object System.Data.SqlClient.SqlCommand;
$MyQuery = $query;
$DataCmd.CommandText = $MyQuery;
$DataCmd.Connection = $Conn;
$DAadapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$DAadapter.SelectCommand = $DataCmd;
$DAadapter.Fill($DTSet) | Out-Null;
for ($i = 0; $i -lt $DTSet.Tables.Count; $i++) {
Write-Host $DTSet.Tables[$i];
}
}
finally
{
$Conn.Close();
$Conn.Dispose();
}
return $DTSet;
}
The internal Write-Host is showing the DataTable name instead of the DataRows.
If I manually create a DataSet with a DataTable in Powershell Console, Write-Host shows me the data in the DataTable rows, so I can't really figure out why it is not doing that in the previous script.
Can you give me some clues on how to show the data contained in the datatables instead of the table names?
Thank you
This piece of code was quite helpful for me, posting it here if anybody needs it.
for ($i = 0; $i -lt $DTSet.Tables.Count; $i++) {
$DTSet.Tables[$i] | format-table | out-host
}
That produces a nice table-like output on screen.

Look through text files for a certain text and insert to SQL

I'm trying to optimize my Powershell Script a little.
I have a lot of log (text) files, that i need to search through the content of, for a specific text entry.
If the entry is found, I need the script to trigger with an inset to an sql databse.
This is what I have for now:
$tidnu = (Get-Date -f dd.MM.yyyy)
$Text = "ERROR MESSAGE STACK"
$PathArray = #()
$NodeName = "SomeName"
$Logfil = "SomeLogFile"
Get-ChildItem $Path -Filter "*ORA11*.log" |
Where-Object { $_.Attributes -ne "Directory"} |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Text)
{
$PathArray += $_.FullName
$cmd.commandtext = "INSERT INTO ErrorTabel (Datotid, Nodename, Logfil, ErrorFound) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "Yes"
$cmd.ExecuteNonQuery()
}
else
{
$cmd.commandtext = "INSERT INTO ErrorTabel (Datotid, Nodename, ErrorFound) VALUES('{0}','{1}','{2}')" -f $tidnu, $NodeName, "No"
$cmd.ExecuteNonQuery()
}
}
This is working okay, but when i need to move to another log file name, i have simply made the same code again with different inputs.
What i would like to do, is to use an Array, and a foreach loop, so i could specify all the log files in one array, like:
$LogArray = #(Log1.log, log2.log, log3.log)
And specify all the Nodenames like:
$NodeArray = #(Node1, Node2, Node3)
And then make a foreach loop that will go through the logfiles one by one and insert into the databse, with it's matching nodename every time the loop runs through.
Can someone help me to make this happen? I have the idea on how it should be done, but I can't figure out how to write the code. All help would be much appreciated.
EDIT:
Ok, this is what i have now then, but i'm not sure that it's correct put together. Its giving me some strange results.
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = "Data Source=PCDK03918;Initial Catalog=Rman;Integrated Security=SSPI;"
$conn.open()
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.connection = $conn
$tidnu = (Get-Date -f dd.MM.yyyy)
$Path = "C:\RMAN"
$Text = "ERROR MESSAGE STACK"
$nodes = #{
'NodeName1' = 'Node1log1.log', 'Node1log2.log', 'Node1log3.log'
'NodeName2' = 'Node2log1.log', 'Node2log2.log'
}
foreach ($NodeName in $nodes.Keys) {
foreach ($Logfil in $nodes[$NodeName]) {
Get-ChildItem $Path -Filter "*.log" |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Text)
{
$cmd.commandtext = "INSERT INTO Error (Datotid, Nodename, Logfil, Error) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "Yes"
$cmd.ExecuteNonQuery()
}
else
{
$cmd.commandtext = "INSERT INTO Error (Datotid, Nodename, Logfil, Error) VALUES('{0}','{1}','{2}','{3}')" -f $tidnu, $NodeName, $Logfil, "No"
$cmd.ExecuteNonQuery()
}
}
}
}
$conn.close()
I have created the log files mentioned in $nodes, in the folder, and put the "ERROR MESSAGE STACK" into Node1log1.log and Node1log2.log The rest of the log files are with no "ERROR MESSAGE STACK" inside.
But the result in the database is strange. It says Error = Yes to log files with no "ERROR MESSAGE STACK" inside, and it says Error = No to the same log files some rows down. Plus its inserting double rows and all in all its not doing as intended.
could it be because my
Get-ChildItem $Path -Filter "*.log" |
is wrong by using *.log ?
Or am I simply going completely wrong about this?
EDIT Once more:
Not sure what I was thinking yesterday, but I believe i have solved it now.
Get-ChildItem $Path -Filter "*.log" |
Will of course not work.
Get-ChildItem $Path -Filter $logfil |
Gives much more sense, and now my databse output is looking much more correct.
#Ansgar Wiechers - Thank you for pointing me in the right direction. I learned alot from this.
Consider using a hashtable for this:
$logs = #{
'Log1.log' = 'Node1'
'Log2.log' = 'Node2'
'Log3.log' = 'Node3'
}
That way you can iterate over the logs like this:
foreach ($Logfil in $logs.Keys) {
$NodeName = $logs[$Logfil]
...
}
If you have more than one log file per node name, it would be more efficient to reverse the mapping and store the log file names in an array:
$nodes = #{
'Node1' = 'Log1.log', 'Log2.log', 'Log3.log'
'Node2' = 'Log4.log', 'Log5.log'
}
Then you can process the logfiles with a nested loop like this:
foreach ($NodeName in $nodes.Keys) {
foreach ($Logfil in $nodes[$NodeName]) {
...
}
}
You should be able to fit your pipeline into either loop without further modifications.
Edit: As an optimization you could do something like this to avoid needlessly fetchin logs with each iteration of the outer loop:
$logs = Get-ChildItem $Path -Filter '*.log'
foreach ($NodeName in $nodes.Keys) {
$logs | ? { $nodes[$NodeName] -contains $_.Name } | % {
...
}
}

Resources