Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
nielsm
Jun 1, 2009



At the very least don't use Add-Content inside the loop like that. You should open all the output files before the loop and keep them open throughout.

Adbot
ADBOT LOVES YOU

nielsm
Jun 1, 2009



He has one "master" workbook that references data in the other workbooks via the INDIRECT function, and to let those formulas recalculate with newest data, he has to open those workbooks too.

nielsm
Jun 1, 2009



PBS posted:

What's the purpose of leaving them open then?

I'm guessing you need to click the "recalculate" button in Excel to actually make it do the update.

nielsm
Jun 1, 2009



Does anyone know of a PS module or method for reading/querying/updating SharePoint lists? The idea being to write tools in PS that fetch some data and store in a SharePoint list for a web-based lookup tool to use.

nielsm
Jun 1, 2009



SharePoint Server (2010 because upgrading is unnecessary busywork)

nielsm
Jun 1, 2009




Thanks, these look great.

nielsm
Jun 1, 2009



Do you need to get this for every user and group object in AD? In that case just use Get-ADObject to return all user and group objects under the relevant OU. Or even just globally. Sort and filter the results afterwards.
Remember to use the -Properties parameter to specify which properties to fetch the values for.

E:f,b

nielsm
Jun 1, 2009



Wouldn't this work?
code:
invoke-command -computername $computers -scriptblock {
    [PSCustomObject]@{
        "Exists" = Test-Path -Path "HKLM:\Software\Microsoft\RPC\Internet";
        "Computer" = [System.Environment]::MachineName
    }
}
In fact, I'm pretty sure PS remoting automatically decorates returned objects with note properties containing the host that produced it.

nielsm fucked around with this message at 20:15 on Nov 29, 2018

nielsm
Jun 1, 2009



Submarine Sandpaper posted:

What's the best way to terminate a script not using throw or exit?

Restart-Computer

nielsm
Jun 1, 2009



Agrikk posted:

Format-List -Property "Name"
This returns one or more formatting objects, that instruct the shell to print the object in a particular way.

code:
Select-Object Name
This creates a new object based on the input, the output object has a single "Name" property.

code:
Select-Object -ExpandProperty Name
This returns the value of the "Name" property of each input object.

code:
ForEach-Object Name
Same effect as the above, just expressed differently, and likely has different performance characteristics. This particular syntax only works in PowerShell 3 and later, but it can also be used to call methods on objects, rather than just extracting properties.

nielsm
Jun 1, 2009



ForEach-Object is a replacement for writing a one-off filter function to insert in a pipeline. foreach statement is a way to repeat a block of statements not connected to a pipeline as such. In my day -to-day use, ForEach-Object is more useful most of the time.

nielsm
Jun 1, 2009



Instead of $foo | % {$_.Trim()} you can simply write $foo | % Trim. Since Powershell 3 ForEach-Object can take the name of a member property or function and will just extract (or call) that on each object.

If you don't need the verbose output you can do just this:

code:
$UserInput = "MARYLAND, GEORGIA,   florida ,norTH CAROLINA,CHEESESTEAK,FLORIDA"
$ValidLocations = @{"FLORIDA" = 1 ;"GEORGIA"  = 2; "SOUTH CAROLINA" = 3; "NORTH CAROLINA" = 4 ; "VIRGINIA"  = 5; "MARYLAND" = 6}
$CustomerLocations = $UserInput.ToUpper() -Split "," | ForEach-Object Trim | Where-Object { $ValidLocations[$_] } | Sort-Object -Unique { $ValidLocations[$_] }
$CustomerLocations -Join ","

nielsm
Jun 1, 2009



Wizard of the Deep posted:

If you're trying to pass by pipeline, I think you should be using "Get-AzureADUser".

Yeah, if it's at all similar to Set-ADUser then Set-AzureADUser does not return anything, unless you also give it the -PassThru flag. If you aren't actually setting anything you should just use Get-AzureADUser instead.

nielsm
Jun 1, 2009



This is not strictly a PowerShell question, but I suspect the regulars here tend to have experience with working with AD programmatically...

The UserAccountControl field on AD accounts has two flags that seem to never be set: LOCKOUT and PASSWORD_EXPIRED
Did they only get set by older versions of AD or even NTDS? If new versions don't set these flags, what's the best way to detect these states?

nielsm
Jun 1, 2009



Ah that computed field is probably what I want then. I'm not actually working in PowerShell for this, but rather using the System.DirectoryServices API from .NET in a C# desktop application, and later after making my previous post discovered that to use the msDS computed properties you have to explicitly request them. Requesting "all properties" in particular does not return them.

nielsm
Jun 1, 2009



Pipe to Measure-Object to count zero, one, and many objects correctly.

nielsm
Jun 1, 2009



Toast Museum posted:

I haven't played with that field myself, but it looks like a somewhat newer field is ms-DS-User-Account-Control-Computed, though that page doesn't indicate implementation beyond 2012, and I'm not sure what has superseded it.

In any case, Search-ADAccount should do what you need with the -LockedOut or -PasswordExpired parameters.

Yeah definitely. These Computed properties are neat but you need to know they are available and only exist when you request them explicitly.

code:
Get-ADUser foo -Properties 'msDS-User-Account-Control-Computed','msDS-UserPasswordExpiryTimeComputed' |
  Select 'Name',
    @{N='Locked';E={($_.'msDS-User-Account-Control-Computed' -band 0x10) -ne 0}},
    @{N='PasswordExpires';E={[DateTime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}}
C# code:
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

var dc = DomainController.FindOne(new DirectoryContext(DirectoryContextType.Domain));
var ds = dc.GetDirectorySearcher();
ds.Filter = "(objectClass=user)(sAMAccountName=foo)";
ds.PropertiesToLoad.AddRange(new string[] {
  "name",
  "msDS-User-Account-Control-Computed",
  "msDS-UserPasswordExpiryTimeComputed",
});
var user = ds.FindOne();
var userName = user.Properties["name"][0] as string;
var userLockedOut = ((long)user.Properties["msDS-User-Account-Control-Computed"][0] & 0x10) != 0;
var userPasswordExpires = DateTime.FromFileTime((long)user.Properties["msDS-UserPasswordExpiryTimeComputed"][0]);

nielsm fucked around with this message at 09:30 on Aug 16, 2019

nielsm
Jun 1, 2009



That looks very much like a "try again later" type error yes. Just handle it and reschedule the job, probably have a limit on number of retries.

nielsm
Jun 1, 2009



The odd thing about the ActiveDirectory module is that it uses a webservice instead of straight LDAP. We definitely have AD servers (running a separate domain) I can manage via the AD Users and Computers MMC-snapin, but can't access via the ActiveDirectory module in PowerShell.

nielsm
Jun 1, 2009



Is this going to run on a client machine through an interactive login? If it is, maybe just use Automation. Basically $outlook = New-Object -ComObject Outlook.Application and go from there.

nielsm
Jun 1, 2009



Toast Museum posted:

If you need to send keystrokes, Write-Host won't get the job done. It sounds like [System.Windows.Forms.SendKeys]::SendWait(string) might work here, though. The documentation for SendKeys.Send shows how to send special characters. As far as identifying when you've reached the end, I'm not sure, since it sounds like there's nothing returned for PowerShell to look out for.

Those functions are only for faking keyboard input to other local GUI applications. They're most likely not suited for controlling an SSH session, especially not one you connected to via PS. It might sort-of work if you used PuTTY or another not-windows-builtin-console application, but then you'd still have the SSH connection pop up on a separate window, and need to figure out a way to read back the contents.

What you want to do is start the ssh process and connects its stdin and stdout to pipes, then read and write from those.

nielsm
Jun 1, 2009



If it's just a one-shot task, I'd reach for a text editor with multi-file search replace.

nielsm
Jun 1, 2009



Depends on the Exchange version, really. As far as I understand the extended AD permission is what Exchange 2007 did, and some update to 2013 changed it to be a MailboxPermission, while still accepting the AD permission. But O365 is always the RecipientPermission.

nielsm
Jun 1, 2009



Question time: Question and answer time:
I'm writing a script module to wrap a web service. Some parts of the web service don't have great lookup features yet, so to find an object by e.g. display name I have to fetch all objects of the class and then filter them myself.

I have something like this:
code:
function Get-Thingy{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$False, ValueFromPipeline=$True, Position=0, ParameterSetName="Single")] $Thingy,
        [Parameter(Mandatory=$False, ParameterSetName="ByName")] [string] $DisplayName,
        [Parameter(Mandatory=$False, ParameterSetName="ByFrob")] [string] $FrobsWith,
        [Parameter(Mandatory=$False, ParameterSetName="All")] [switch] $All
    )
    Process {
        if ($PSCmdlet.ParameterSetName -eq "Single") {
            $ThingyGuid = [GUID]::Empty
            if ($Thingy.GetType().FullName -eq "System.Guid") {
                $ThingyGuid = $Thingy
            } elseif ($Thingy.Uid) {
                $ThingyGuid = [GUID]$Thingy.Uid
            } elseif ([GUID]::TryParse($Thingy, [ref]$ThingyGuid)) {
            }
            if ($ThingyGuid -ne [GUID]::Empty) {
                Invoke-RestMethod -UseDefaultCredentials  -Uri "$script:UrlBase/api/v1/thingy/$ThingyGuid" -Method GET
            }
        } elseif ($PSCmdlet.ParameterSetName -eq "ByName") {
            Get-Thingy -All | Where DisplayName -like $DisplayName
        } elseif ($PSCmdlet.ParameterSetName -eq "ByFrob") {
            Get-Thingy -All | Where FrobsWith -like $FrobsWith
        } elseif ($All) {
            Invoke-RestMethod -UseDefaultCredentials  -Uri "$script:UrlBase/api/v1/thingy" -Method GET
        }
    }
}
It works with the Single and All parameter sets when used by itself.
But when I try the ByName or ByFrob parameter set I get nothing back.

Fake edit: I found the answer while writing this post, so leaving it here so anyone else can maybe learn too.
The issue is that Invoke-RestMethod technically returns a object[] array instead of a PowerShell collection or PSCustomObject, when the web service call returns a JSON array.
You need to do "something" to the result to force it to convert into a regular PowerShell collection of PSCustomObject data. One thing you can do is simply wrap the call in parentheses, like this:
code:
(Invoke-RestMethod -UseDefaultCredentials  -Uri "$script:UrlBase/api/v1/thingy" -Method GET)
Tada now everything works.

By the way this is on PowerShell 5.1.17763.1007 on Windows 10 1809. Maybe it's fixed/improved in the newer versions.

nielsm
Jun 1, 2009



Toshimo posted:

Ok, I've been basking my face into this for hours, and I'm trying to do a registry import "the PowerShell way" using New-ItemProperty, and it's all fine and normal until I try to make a Binary value.

This works for me:
code:
$newPropertyValue = [byte[]](1,2,3,4,5)
Set-ItemProperty -Path . -Name "test" -Value $newPropertyValue -Type Binary

nielsm
Jun 1, 2009



First, check what's going on with your copy-paste. All the line breaks are double on the forums, and the indentation looks uneven, especially in the documentation block. (Also, 16 spaces indentation is weird too.)

As far as I can tell from reading the code, if you send in services by the pipeline, they're going to be processed sequentially with waiting for the timeout for each individual service. It would run the timeouts in parallel only if you passed the services as an array in a single parameter.
If your intention is to process the timeout for all the service stopping in parallel then you'd have to only start those jobs in the Process{} block, and then do all the remaining work in the End{} block, which runs after the pipeline input to the cmdlet has finished.

The basics of CmdletBinding and functions with Process{} that take input from the pipeline, is that the Process{} block is an implicit loop over each object on the pipeline, so treat it as that.
The exception to that is if the type of the pipeline parameter allows array types and you pass the input object as an array as a parameter instead of through pipeline, then the Process{} block runs once with the array as input, and in that case you'd have to do an internal foreach loop over that.
So what I want to say is, if you want to allow passing an array of services as a parameter and have it work, then keep the foreach loop inside the Process{} block too.

Your return values are odd.
The best way to return objects to the pipeline is actually to not use the return statement but just leave them to output.
Additionally, you should normally not return a hashtable, but use the [PSCustomObject] pattern to convert hashtables to objects that behave nicer and work with the standard formatting and output cmdlets.

Consider the standard PowerShell function naming scheme, "Verb-Noun". Stuffing extra terms in front of the verb is bad practice.

PowerShell code:
function Stop-CSSDTService {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
    $Service,
    [Parameter()]
    [int] $Timeout = 30
  )

  Begin {
    $jobs = @()
  }

  Process {
    foreach ($s in $Service) {
      Write-Host "Stopping service $($s.Name)"
      $jobs =+ Start-Job -ScriptBlock {Stop-Service $input.Name } -inputObject $current_service
    }
  }

  End {
    Write-Host "Waiting up to $Timeout seconds for jobs to process..."
    $jobs | Wait-Job -Timeout -$Timeout | Out-Null

    $exitCode = $true
    $exitErrors = @()
    foreach ($j in (Get-Job $jobs)) {
      switch ($j.State) {
        "NotStarted" { $exitCode = $False; $exitErrors += "Error: Job not Started ($($current_job.Name)" }
        "Running" { $exitCode = $False; $exitErrors += "Error: Job Timed Out ($($current_job.Name)" }
        "Failed" { $exitCode = $False; $exitErrors += "Error: Failed ($($current_job.Name)" }
        "Blocked" { $exitCode = $False; $exitErrors += "Error: Job Blocked ($($current_job.Name)" }
      }

      if ($j.Error) {
        $exitCode = $false
        $exitErrors += "Error: Stop-Service command returned error: $($current_job.error)"
      }
    }

    # etc... missing code

    [PSCustomObject]@{
      ExitCode = $exitCode;
      Errors = $exitErrors;
    }
  }
}
The entire way of returning the status seems a little odd to me, but I'm not sure if you intend to connect it with other things on pipeline that want to consume the output.
It's often a good idea to try to structure data all the way through, including returns, rather than formatting for text display only.

nielsm
Jun 1, 2009



I'm working on a module to provide an interface for calling a webservice and do batch changes to some data.
However, this webservice has some annoying rate limits, but does support batching multiple operations into a single request. So if I can batch my changes, I can work through any data much faster.

Do anyone know of tools or techniques to split a pipeline of individual objects in PowerShell into blocks of some size, say 20 or 100 objects per block, and then process them per block instead of individually?

nielsm
Jun 1, 2009



Yes if you can, connecting to MS SQL Server with Integrated Authentication is often the easiest and most secure. Then you just need your process to run under the appropriate user token, such as a service or batch job account, or you can use Run As to start as another user.

Adbot
ADBOT LOVES YOU

nielsm
Jun 1, 2009



I'm not sure I understand, do you mean like an array of arrays?

code:
[
  ["iPad", "a long udid", "", 663, "iPad", "A5035", "2023-07-01 22:59:23", "XXXXXXXXXX", 50],
  ["iPad 2", "another udid", "", 842, "iPad 2", "A5035", "2023-07-01 22:59:25", "XXXXXXXXXX", 98]
]
Like that, with no header information?

Since I assume you've already tried ConvertTo-Json directly on your data and decided that it will not work.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply