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
FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Is there any hope of me understanding this moonspeak if I "think" in Python already? This is mostly just indecipherable garbage, and it didn't help that by default PowerShell won't run PowerShell scripts

I'm trying to run a command on a bunch of files in a directory, this is the command, as outputed by the mkvmerge gui:

quote:

"C:\Program Files (x86)\MKVToolNix\mkvmerge.exe" -o "C:\\Users\\me\\Desktop\\bad folder\\file.mkv" "--default-track" "0:yes" "--forced-track" "0:no" "--display-dimensions" "0:1280x720" "--language" "2:eng" "--default-track" "2:yes" "--forced-track" "2:no" "-a" "2" "-d" "0" "-S" "-T" "--no-global-tags" "--no-chapters" "C:\\Users\\me\\Desktop\\good folder\\file.mkv" "--track-order" "0:0,0:2"

It's big, and it's nasty, and for some reason it's got double quotes puked out all over it. What I'm doing is removing a language track from each mkv, then outputing it to a new directory with the same name. I could write a script to generate all 24 mkvmerge commands in Python in the time it's taken me to try and figure this out, but since I guess I'm a Windows admin I should at least try and figure this out the "right" way?

Adbot
ADBOT LOVES YOU

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I'm using cwrsync to pull some files from a Unix server, and I've decided to do this little project in Powershell, because why not.

To get it to run I have to modify some enviroment variables, which I can do in a bat script like this:
code:
SET HOME=E:\folder
SET PATH=%programfiles(x86)%\cwrsync\bin;%PATH%
rsync -av -e "ssh -i e:\ssh\id_rsa" unixhost:~/file .
Now I'm trying to duplicate this in powershell. I've found the $env and used that to modify the PATH variable, but I can't figure out how I need to invoke the rsync command so that it looks in that path.

I can't just run it with the full path because it keeps grabbing at binaries in PATH to figure out what to do (insert: I bet if I specified full path for both rsync and ssh it would work, but I'd rather just figure this out).

So, what am I missing here?

E: Solved my own problem. This is what I was doing:
code:
$env:path = "$env:programfiles(x86)" + "\cwrsync\bin;" + $env:path
For some reason it was interpreting "$env:programfiles(x86)" as C:\ProgramFiles(x86) (which isn't a real path). This instead worked:
code:
$env:path = ${env:programfiles(x86)} + "\cwrsync\bin;" + $env:path
Now it's in my path, and I can call the rsync binary just fine.

FISHMANPET fucked around with this message at 23:08 on May 25, 2012

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Alright, powershell is pissing me off right now.

I created a bunch of accounts with a python script that called dsadd, but I hosed it up and now I'm trying to fix the profile paths. I want to find all the profile paths that are on server2, instead of server1.

So I say to myself, this sounds perfect for Powershell! Except Powershell can't read the profile paths out of most of my accounts, they just show up as null. dsget works just fine, and looking at them in AD Users & Computers I see the bad profile path, but get-aduser shows nothing.

Nevermind what a loving task it was to get powershell to even search. And nevermind how impossible Microsoft has made it to actually get the AD cmdlets. Why in the hell would I have to run a special instance of Powershell (Active Directory Module for Windows PowerShell) to actually be able to query AD. Shouldn't running Powershell on the DC be enough?

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

Nebulis01 posted:

Curious, if you have time would you spin up a Server 2012 DC in test and see if the query succeeds in powershell v3? It's supposed to have improved/fixed a bunch of this poo poo

I'll see if I can give it a try, as long as I don't need to put it into DNS, because AD doesn't control DNS, we have to go to the dark overlords to get SRV records put in.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

Misogynist posted:

Import-Module ActiveDirectory

Get-ADUser -Filter * -Properties profilePath | Where-Object { $_.ProfilePath -match '\\server2' }

That would have been just fine, except for a lot of the accounts the ProfilePath was listed as empty.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
From what I can tell, permissions in PowerShell are pretty awful, I had a script that modified permissions, and I just ended up calling xcacls in my script.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
You could use -PassThru, which will return the created object. My guess is that if it fails it will just fail and die out.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
To up one level you would go to .. so in your example it would be $LogPath = "..\Logs". To go up two levels just keep doing the same, "..\..\Logs" etc etc.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
This language is such garbage. Never have I dealt with a language where I have to spend 90% of my time just fighting the language because it doesn't behave rationally.

I'm trying to change the local admin password on a bunch of servers. For reasons I don't understand WMIC isn't working so I'm trying to use invoke-command to run a command on the remote machine. I want to pass my script a list of servers that I want it to change the password on, and then I want it to return a result of what happens on each server, either sucess or failure.

I keep wrapping things in try-catch blocsk and invoke-command just returns garbage as far as error handling goes. I want invoke-command to return an error, but the only way it will return an error is if the the computer you're running on doesn't exist. And it doesn't actually return an error that can be caught, I have to run it as a job and then do some fuckery to get an actual error out of it. If I want to get an error out of my script block that I'm running on the remote machine, that's a whole other can of worms. This doesn't seem that complicated, and one of those vaunted use cases that Powershell is supposedly great for. Automate a task on a bunch of servers, and let me know how it goes (is it the wanting to know how it goes part that I'm not supposed to be doing with powershell?)

I'm about ready to find something to treat as a physical manifestation of powershell, so I can throw it across the room because it's all garbage. I've been fighting all week with this and all Powershell does is get in my way. Maybe it's because I'm used to programming in other languages that actually make sense? Do I need a lobotomy to think in Powershell terms?

Here's my code, for reference, maybe I'm going about this entirely the wrong way?

code:
Param(
    [Parameter(Mandatory=$true)]
    [String[]]
    $ComputerNames,

    [Parameter(Mandatory=$true)]
    [String]
    $LocalAccount
)

$password = Read-Host -Prompt "Enter Password Base" -AsSecureString

$RemoteChangeScript = {
    Param(
        [Parameter(Mandatory=$true, Position=1)]
        [SecureString]
        $Password,

        [Parameter(Mandatory=$true, Position=2)]
        [String]
        $LocalAccount,

        #This is here so I can record what the server name that the script connected to was, sometimes the DNS records get messed up, it can be nice to have this.
        [Parameter(Mandatory=$true, Position=3)]
        [String]
        $TargettedServerName
    )
    $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true" | Foreach {$_.Name}
    foreach ($User in $LocalUsers)
        {
            if ($LocalAccount -ne $User)
            {
                Write-Warning "Server: '$($TargettedServerName)' has a local account '$($User)' whos password is NOT being changed by this script"
            }
        }

    $password.AppendChar($env:computername[-1].ToString().ToLower())
    $password.AppendChar($env:computername[-2].ToString().ToLower())
    try
    {
        $objUser = [ADSI]"WinNT://localhost/$($LocalAccount), user"
        $objUser.psbase.Invoke("SetPassword", [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)))
        $Properties = @{
            TargettedServerName = $TargettedServerName
            Username =  $LocalAccount
            RealServerName = $env:computername
            Error = "Sucess"
        }
    }
    catch
    {
        Write-Error "Error changing password for user:$($LocalAccount) on server:$($TargettedServerName)"
        $Properties = @{
            TargettedServerName = $TargettedServerName
            Username =  $LocalAccount
            RealServerName = $env:computername
            Error = $_.Exception.Message
        }

    }
    finally
    {
        
        $ReturnData = New-Object PSObject -Property $Properties
        $ReturnData
    }
}
    
    foreach ($Computer in $ComputerNames)
    {
        $Return = Invoke-Command -ScriptBlock $RemoteChangeScript -ArgumentList @($password, $LocalAccount, $Computer) -ComputerName $Computer -AsJob
        #$Return = Invoke-Command -ScriptBlock {echo "helo"} -ComputerName $Computer -AsJob
        try
        {
            Write-Warning "$computer try"
            $Result = Receive-Job $Return -ErrorAction Stop
            #Write-Warning $result
            Write-Warning "hello this is the try block"
            #Write-Warning $result
        }
        catch
        {
            Write-Warning "$computer catch"
            $Properties = @{
                TargettedServerName = $Computer
                Username =  $LocalAccount
                RealServerName = $Computer
                Error = $_.Exception.Message
            }
            $Result = New-Object PSObject -Property $Properties
        }
        Write-Output $Result
    }

    #$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword))

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I don't think our firewall is allowing the ADSI connection through, but I can't really be sure because the error I get is not especially useful:
code:
The following exception occurred while retrieving member "SetPassword": "The network path was not found.
"
At line:1 char:1
+ $objuser.SetPassword("password")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ExtendedTypeSystemException
    + FullyQualifiedErrorId : CatchFromBaseGetMember
And that's not really a useful error message, the machine clearly exists. I can't find any useful information on what this error means to be able to debug it.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

Briantist posted:

What about something like this?

I took out -AsJob.

You're already returning an object which is good, and it already has an error property, even better.

As you pointed out, you can't throw an exception on the remote side and catch it on the local side. That's because it's a completely different session or runspace. Any objects sent back are serialized and so are no longer "live".

So I changed the code a bit so that when there is no error, it just sets the error property in your object to $null. When there is an exception, just use $_ and don't bother with the internal exception's message (right now we just want to pass this back to the caller).

Then back on the calling side, you check for an error in the property. At this point, you could JUST use this method (an if statement) instead of try/catch, but I'm assuming you want to use try/catch so if the error exists, then we just throw the error as is (which you can then catch and handle as necessary).

I haven't tested this yet, but it's fairly workable and much easier than using jobs.

Jobs give the added benefit of asynchronous-ness but your code as is could easily fail if the remote job were not yet completed. So you'd need more code to check on the status of the jobs that are waiting and receive the results when necessary.

So I put this aside for a few days, and came back to it on Monday. The reason I was using -AsJob was that I was trying to catch the case where invoke-command couldn't connect to the machine for whatever reason. But after letting it sit for a while and coming back, I decided to add "-ErrorAction Stop" to the Invoke-Command so now it stops and properly throws an error when it can't connect.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Mapped drives are per user, and when you run as admin you're running as another user, and that other user doesn't have the drives that your user does.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Find the lines where it sets the subject of the email and add another variable to that. You can pretty easily get the hostname of a computer using built in powershell variables.
Since you're new and sound like you want to learn I'm being kind of vague in the hopes that it will point you in the right direction of figuring it out yourself. Teach a man to fish blah blah blah. Also I'm too lazy to download the script myself and look at it ;)

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
So I'm writing a script to cleanup from an SCCM bug and I'm wondering how to approach the problem.

The problem is that SCCM has created multiple folders that have identical contents and I need to find them. The folders have random names, but they're named with a GUID. So I'd probably need a regex to say "is this a GUID of form X" or not. I also don't know how many levels deep the folders go. So it will look something like this
code:
Folder A
-Folder B
--Folder C
---Folder ???
----{GUID 1}
----{GUID 2}
So I need to find a folder that's full of GUID folders, then iterate over every GUID folder and match identical contents. I'm thinking for that a double for-each loop, so for each GUID folder I compare to every other GUID folder. But I'm just wondering what would be an easy way to compare the folders in Powershell? The files are identical, the same number, the same size, I'd imagine the hashes are the same (but I don't want to hash gigabytes of data for no reason). Any tips?

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
The specific bug is that when it imported a driver folder, it makes a copy of the folder for every INF file. the worst case was a 500mb Realtek driver that had 40 INF files so it made 40 copies of that folder, resulting in a 20Gb driver pack. So yeah SCCM spewed too many files out, but I don't know what files specifically it spewed, I just know that it if something is there twice then I need to mark it as bad (remediation will be done manually because it's kind of involved and scripting in SCCM sucks).

But anyway I've got another thing I have to work on today before I can dig into this but hopefully I can get to it yet today and figure out what's going on.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
tl;dr I'm an idiot don't mind me

OK I just have no idea what's going on, but every time I print anything, "Can't find file " gets put at the beggining.
code:
function Get-FolderSize ($Directory)
{
    $return = (Get-ChildItem -path $Directory.FullName -recurse | Measure-Object -property length -sum).Sum
    $return
}

$folders = gci C:\Temp
foreach($folder1 in $folders)
    {
    #print $folder1
    $name = [GUID]::Empty
    if([Guid]::TryParse($folder1.Name,[ref]$name))
    {
        #print $folder1.Name
        foreach($folder2 in $folders) 
        {
            #print $folder2.Name
            if ((Get-FolderSize $folder1) -eq (Get-FolderSize $folder2))
            {
                #print "$folder1 and $folder2 match!"
                print "match!"
            }
        }
             
    }
}
This should just output the word "match!" a bunch of times because the data I'm running it against has the problem, but instead it outputs "Can't find file match!" instead. If I uncomment any of the other print statements, it tries to put "Can't find file " at the beginning of those too. I don't have any idea what's going on here.

E: AAAAUGH this has infected my entire environment! If I log out and log in, open a powershell window, and just write print "hello" in the window I get back "Can't find file hello"

E2: Why am I using print? Did I fall on my head and think I was writing Python? I guess I'm an idiot and I keep calling the "print" command.

FISHMANPET fucked around with this message at 22:07 on Sep 21, 2015

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
That's kind of impressive, but also shouldn't have worked as written because I was only "printing" the word match.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Welp, just printing out every file in your Temp dir, nbd.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I had to solve a similair problem, mine was changing the password on 1000 servers running everywhere from Server 2003 up to 2012 R2. I was also interested in catching the various failure states.
code:
Param(
    [Parameter(Mandatory=$true)]
    [String[]]
    $ComputerNames,

    [Parameter(Mandatory=$true)]
    [String]
    $LocalAccount
)

$input = Read-Host -Prompt "Enter Password Base" -AsSecureString
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($input))
$input = Read-Host -Prompt "Enter Password Base Again" -AsSecureString
$password1 = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($input))
    if ($password -ne $password1) {
        Write-Error "passwords don't match"
        Exit
    }

$RemoteChangeScript = {
    Param(
        [Parameter(Mandatory=$true, Position=1)]
        [String]
        $password,

        [Parameter(Mandatory=$true, Position=2)]
        [String]
        $LocalAccount,

        #This is here so I can record what the server name that the script connected to was, sometimes the DNS records get messed up, it can be nice to have this.
        [Parameter(Mandatory=$true, Position=3)]
        [String]
        $TargettedServerName
    )
    $LocalUsers = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=true" | Foreach {$_.Name}
    foreach ($User in $LocalUsers)
        {
            if (($LocalAccount -ne $User) -and ($User -ne "Guest"))
            {
                Write-Warning "Server: '$($TargettedServerName)' has a local account '$($User)' whos password is NOT being changed by this script"
            }
        }
    $password = $password + $env:computername[-2].ToString().ToLower() + $env:computername[-1].ToString().ToLower()
    try
    {
        $objUser = [ADSI]"WinNT://localhost/$($LocalAccount), user"
        $objUser.psbase.Invoke("SetPassword", $password)
        $Properties = @{
            TargettedServerName = $TargettedServerName
            Username =  $LocalAccount
            RealServerName = $env:computername
            Error = "Sucess"
        }
    }
    catch
    {
        Write-Warning "Error changing password for user:$($LocalAccount) on server:$($TargettedServerName)"
        $Properties = @{
            TargettedServerName = $TargettedServerName
            Username =  $LocalAccount
            RealServerName = $env:computername
            Error = $_.Exception.Message
        }

    }
    finally
    {
        #Write-Warning $Properties.RealServerName
        $ReturnData = New-Object PSObject -Property $Properties
        Write-Output $ReturnData
    }
}
    
    foreach ($Computer in $ComputerNames)
    {
        #$Return = Invoke-Command -ScriptBlock {echo "helo"} -ComputerName $Computer -AsJob
        try
        {
            $Result = Invoke-Command -ScriptBlock $RemoteChangeScript -ArgumentList @($password, $LocalAccount, $Computer) -ComputerName $Computer -ErrorAction Stop
            #Write-Warning "$computer try"
            #$Result = Receive-Job $Return -ErrorAction Stop
            #Write-Warning $result
            #Write-Warning "hello this is the try block"
            #Write-Warning $result
        }
        catch
        {
            #Write-Warning "$computer catch"
            $Properties = @{
                TargettedServerName = $Computer
                Username =  $LocalAccount
                RealServerName = $Computer
                Error = $_.Exception.Message
            }
            $Result = New-Object PSObject -Property $Properties
        }
        Write-Output $Result
    }

    #$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword))
There's a lot of ancillary stuff here that was specific to to my task but I'm not going to take it out because I'll probably just turn the code into nonsense. BUT
Basically:
I created $RemoteChangeScript, which is the script I would execute on the remote machine. Then I used a for each loop to go through each computer and try and use invoke-command on that computer. If it failed and went to my catch block, I created a new object that had the computer name (twice, because I got the name 2 ways) and the name of the account I was trying to change, as well as the specific error message, and then returned that. In the RemoteChangeScript if my command to change the password succeeded, I returned a similar object with an "error" message of "success. And if the script failed on the remote computer, I had a catch block that would return an object with that error. So no matter what, my script would return an object with the computer name, the account I was changing, the computer name again, and what the outcome of the command was. This let me separate the successes from the computers I couldn't connect to from the computers that for whatever reason couldn't execute the command locally.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I'm confused why you can't just pipe this into import-csv. Is there a reason you need a singular array rather than a list of objects that can be treated like an array?

I suppose that would get you each field as a string, and you'd have to manually split it up into an array and maybe even make a new object but that still seems easier than what you're trying to do.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I've been writing all my output to CSV in my scripts because it's been for human consumption. But I discovered that if one of the properties of your exported objects is an array, export-csv doesn't like that (just prints System.Object[] instead the values in the array). So now I've discovered outputting XML and JSON. This changes everything!

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I've reached the limit of where the SCCM Cmdlets can take me, so I'm having to branch out into WMI queries for some of my stuff. And I have to say I'm just sort of lost. If I'm lucky enough to find someone online doing the same thing as I am I can just copy their query. But once I try and modify them I get lost, and I'm not really sure where to look for more help. I can find the MSDN documentation on the WMI classes, but that's pretty useless. Maybe I need a language primer/tutorial? Not really sure. I know some basic SQL so it's not like a WQL query is completely foreign to me.

Here's the specific problem I'm working with. First, consider this:
code:
$ContentData = Get-WmiObject -Namespace "root\SMS\site_$($SiteCode)" -Query "SELECT SMS_PackageToContent.ContentID from SMS_PackageToContent JOIN SMS_CIToContent on SMS_CIToContent.ContentID = SMS_PackageToContent.ContentID where SMS_CIToContent.CI_ID in ($($CIID))" -ComputerName $SiteServer -ErrorAction Stop
So that works. I honestly don't understand the join that's happening, but it successfully returns the things that have the CI_ID $CIID.

Ok I figured out the join and why I don't need it, and rewrote the query like this:
code:
$ContentData = get-wmiobject -namespace "root\SMS\site_$($SiteCode)" -query "Select * from SMS_CIToContent where CI_ID = $CI_ID" -ComputerName $SiteServer
So I'm trying to speed my script up, because it has 1000 of those (actually exactly 1000, oddly enough). So instead of running through a for loop where I run that query 1000 times, I'm trying to pass in an array of CI_IDs. But I don't know if that's even possible, and if it is possible, how I would go about modifying the "WHERE" clause.

To add more confusion to this, when I try and find articles about the WHERE clause, "in" isn't one of the listed operators, and I in fact found a Stackoverflow question that is my exact question, and the answer was "WQL doesn't have an IN operator" and yet there it is and it works. So I can't even really do research on what the found code is doing, because apparently what it's doing is impossible!
http://stackoverflow.com/questions/19530825/in-operator-in-wql

FISHMANPET fucked around with this message at 23:53 on Oct 16, 2015

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I'm back with another optimization question! (I gave up on that last one and just let the loop run 1000 queries because it was basically a one off thing)

So now I'm working on a script to check some stuff in SCCM and notify people if they're doing dumb stuff, so this will be running at regular intervals. Part of it is that I have an object array of 1600 Collection objects. If you know anything about SCCM, we have a complex setup of limiting collections, and I'm trying to find the "root" limiting collection for each collection. So I have this poor man's recursion:

code:
function Get-CMUnit($collection) {
    $loopCollection = $collection
    do {
        $limit = $loopCollection.LimitToCollectionName
        if ($limit -eq "All Systems") {
            $properties = @{
                Collection = $collection.Name
                Scope = $loopCollection.Name
            }
            $scope = New-Object pscustomobject -Property $properties
        } else {
            $loopCollection = $collections | where-object {$_.Name -eq $loopCollection.LimitToCollectionName}
        }
    } until ($scope)
    $scope
I then call this in a foreach loop for all 1600 Collections. Basically, for each collection, if it's "limiting collection" is "All Systems" (the base) it returns the name of itself. Otherwise, it looks through the 1600 length object array for the limiting collection, so it can see if that collection is limited to All Systems. This script takes about an hour to run on my laptop, and based on a counter I just put in and having it run for a few minutes, it searches the 1600 item object array 3000-5000 times. So I'm looking for a way to speed that up. There's a whole bunch of properties in each collection object, most of which I don't need. Would it be faster if I used select-object to filter to only the properties I care about? Is there some other way I could be doing this to speed it up?

It'll run at night so ultimately I don't care how long it takes but it's a bear to test and if I can optimize it that'd be good either way, and also learning new things is good too.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
So I know that this line specifically is what's taking so long, because it's where I'm searching through 1600 items:
code:
$loopCollection = $collections | where-object {$_.Name -eq $loopCollection.LimitToCollectionName}
I changed the format of the where-object command and cut the speed by 60%. Using the function method finding the scope on the first 100 items took 5.08 minutes.

With this code:
code:
$loopCollection = $collections | where-object -Property Name -EQ -Value $loopCollection.LimitToCollectionName
On the first 100 items that took 2.10 minutes. So that's pretty good.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
And Victory!

I limited the properties in $collections to only those I wanted. So basically:
code:
$collections = $collections | select-object Name,LimitedToCollectionName,RefreshSchedule
With that little change, it took 4.5 seconds to operate on the first 100 items, and 1.21 minutes to operate on the entire set of 1600 items.

So the moral of the story is, the bigger your object, the longer where-object spends looking through it!

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
You'll probably want a while loop:
code:
while (Get-Mailboxexportrequest -neq ???) {
sleep 60
}
So I don't know what the Get command will look like but if you can generate a single command to figure out if it's complete or not, put that in the while. While that command returns true, it will execute what's in the loop. Once that command returns false, it will exit the loop.

If it's more complicated to figure out if it's done or not, you can use a do while (or do until) loop. A do loop will always execute at least once, so you can put in code to check if it's done and put that into a binary variable, and then use that variable in the while or until. While and until are just antonyms, so while ($variable) is the same as until (-not $variable).

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I'm trying to construct the parameters for an exe and executing it from within Powershell. For reference, I'm using CreateMedia.exe in SCCM: https://msdn.microsoft.com/en-us/library/jj155402.aspx

I'm having trouble getting quotation marks to appear in the right places. I'm using the call (&) operator. To start I just manually created the command line and parameters I needed.
code:
& "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CreateMedia.exe" /K:boot /S:"SMS" /L:"Configuration Manager 2012" ...
There's a lot more but that's enough to get the point across. So that works. Now I want to make this more dynamic. Since the call operator doesn't do any interpretation, I need to construct my paramater strings before I call the command.
code:
$cmd = "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\i386\CreateMedia.exe"
$typearg = "/K:boot"
$sitecode = "/S:`"SMS`""
$label = "/L:`"Configuration Manager 2012`""
I'm using double quotes and the escape character because I want my strings to contain the double quotes, and some of the variables will be made up of other variables. (If I used single quotes it wouldn't interpret the variables and just take them literally, right?)
So then I use these variables to build the call command.
code:
& $cmd $typearg $sitecode $label
And this is where things fall off the wheels. It appears that the command it passes has the double quotes stripped out, because I get an error:
Invalid parameter: Manager

So, am I constructing this incorrectly? Is there another way I should be doing this?

E: Found this article that includes a tool that will just print the arguments as passed, and it looks like the quotes are preserved but there are some extra ones, so now I'm not entirely sure what's going on.
E2: And I was wrong about not being able to use variables in command parameters, I misinterpreted what I read. So I have very little idea what I'm doing here apparently.

FISHMANPET fucked around with this message at 18:48 on Jan 7, 2016

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
So that appears to pass the string the right way to the showargs.exe, but somehow fails miserably with createmedia.exe. The createmedia.exe logs the full parameter list it's given (sort of, it strips the quotes in the logs) and the parameters it understands, and it apparently wasn't able to read anything from that.
E: Screw it, took my hand constructed string, dropped the variables I actually want to change, and it works just fine.
code:
& $cmd /S:"$sitecode" [etc etc]

FISHMANPET fucked around with this message at 20:12 on Jan 7, 2016

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I think the author of the cmdlet has to write in support for -whatif, so that command just does it poorly. Are you having actual roubles executing the command, and/or is there a reason you're not using Disable-NetAdapterVmq?

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I would just like to chime in and say that I absolutely detest The ? and % in powershell (and similar constructs in other languages) because they help you write unreadable code. Sure it's nice to do on the command line, but if you're writing a script that any body has to look at ever (including yourself) for the love of God please use the full command name so that you can actually read the code. The pipe is forgiveable because it's a core feature of the langauge, but $_ bothers me as well (especially when you start nesting statements, it's hard to figure out what $_ will actually mean at any given point).

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
You need to tell it what to export. I assume you want $AllADUsers exported?
out-host -paging $AllADUsers or export-csv -path $path -NoTypeInformation $AllADUsers

Oh yeah you're not even setting your output to anything. Assuming that select line actually finds something, you'd need to pipe that into out-host or export-csv.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
I've got a style question. I've got a script, and it does some things, but the person running it may not care about those things. So I'm using write-verbose. And I've got [CmdletBinding()] at the top of my script, so it can accept the -verbose flag. But when I do that, every command I'm running outputs verbose output as well, overwhelming my output!

I could attach -Verbose:$false to every command I call to scilence it, but that sounds bad. I would just say screw it and write-host (problematic) or write-out my output. What's the "proper" powershell way to do this?

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
OK I'm tearing my hair out over here and I just have no idea how I'm supposed to move forward.

We have a powershell script. We run it to build new machines. We have an Enterprise Github subscription so the script is "stored" there. Our team (7 of us) share a single RDS server that we use as a tools server it's where all our "stuff" is. I want to keep our script in a place on that server, such that it will always be the latest version. Github is the "source of truth" so to speak and there should always be a copy of that source of truth on our tools server.

I've come up with a number of possibilities on how to solve this problem, and they all seem bad, so I'm worried that something we're doing is wrong and should be done differently.
The dumbest brute force method is to have a scheduled task that runs every <unit of time> to execute a script that does a git pull. That's totally non-elegant, and there's the (minor) issue of what happens when the script is updated in Github but the scheduled task hasn't been run yet.
I could setup a github post-commit webhook. I could then either send that webhook to an azure automation service that would then fire off a git pull on the tools server. Or I could custom write something in powershell that will listen for that webhook and do a git pull when it receives the notification. Both of those seem convoluted and require a lot of custom programming.
I could go all out and setup a CI process. I've gone slightly down the rabbit hole of looking at Visual Studio Team Services, but this is dramatically overkill, and is in no way aimed at a sys admin, but at a programmer. I could make it work, but there don't appear to be any built in "build" actions that say "copy this git repo when it changes" so I'm left writing my own script to do the git pull and having VSTS execute that when the repo changes.

None of this stuff is particularly complicated (maybe writing a powershell service to listen for a web request is...) but it's still special snowflake code that I have to write myself rather than just copy from someone else smarter than me on the internet. Which is kind of the root of my problem, this doesn't seem like an uncommon scenario yet I can't find any information about how to easily solve this problem so I'm left wondering if I'm solving the wrong problem, in which case what should I be doing differently?

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Does anyone know of a good resources for "best practices" around error handling? I understand the mechanics of the try-catch block but at work we've just got so much code that catches the error and throws "error: error" or some nonsense like that and I'm sure other people have solved this problem beyond "throw it all in a try-catch block and shrug your shoulders"

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
This is all just in scripts we run, and I'm absolutely tired of seeing poo poo like this:

code:
    try                               
        {        
            #do stuff  in a loop
                Try
                    {
                           # do more stuff
                    }
                Catch {Write-Warning "Failed to send mail message to $target"} 
        }
    Catch {Write-Warning "Unable to email to $department"}
And I'd really like to write some standards, like, if you're gonna use a try-catch block this is what you should do with the error etc. Stuff like this just obfuscates it and makes it 100 times harder to debug.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Is the problem all 68 workbooks opening at the same time, or having all 68 workbooks open at the same time? If it's just that launching them all at once causes your system to freak out, I'd add start-sleep -seconds 5 after every invoke item. This will cause your script to wait 5 seconds between calls of invoke-item.

Also, powershell is way better at iterating than the way you're doing it. This doesn't really change how your script runs but it's much better powershell:
code:
$string = "*string containing wildcards*.xlsm"
$workbooks = Get-ChildItem -Path "\\my\file\path\goeshere" -Include $string -Recurse
foreach ($workbook in $workbooks)
    { 
      Invoke-Item $workbook
      start-sleep -seconds 5
    }
I guess you do lose your iterator i but I suspect you don't actually need that.

FISHMANPET fucked around with this message at 19:18 on Aug 24, 2018

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
Do people have thoughts on foreach vs ForEach-Object? Is one or the other more readable? Better performing? More powershelly?

I prefer foreach because it reads better to me but that could be the last vestiges of Python being my native language. My coworker uses ForEach-Object and it looks kinda dirty and messy but piping does seem more in line with doing things the Powershell way.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
fam just gently caress me up
code:
foreach ($i in (0..($array.Length-1))) {
    $array[$i]
}

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
See I have a CS degree so I've done tons of programming in languages (C, C++, Python) with that for loop formulation, so the first few times I wrote one in Powershell I really had to scratch my head to wrap it around it.

Adbot
ADBOT LOVES YOU

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
That seems way harder to read and understand compared to what you originally wrote.

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