If you are getting BINDLSVC error 522 errors in the event log when attempting to boot systems into WDS you may have a bad config in AD.
Normally this is because when setting values to the netbootMirrorDataFile attribute on the computer object the ; (semicolon) at the end of line may be missing.
From MSDN:
netbootMirrorDataFile: This attribute is used to store multiple values in the following format:
<key-1>=<value-1>; <key-2>=<value-2>;...; <key-N>=<value-N>;
Thursday, August 15, 2013
Wednesday, August 14, 2013
Using C# to manage a Brocade TurboIron switch via SNMP
This is to provide an example of a plug-in I wrote for the Green Monster System to manage a Brocade TurboIron switch.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
public bool createVlan(int tag, string name)
{
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.1." + tag, name, datatypes.str)); //Set Vlan Name
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.2." + tag, new Byte[1], datatypes.str)); //Egress Members
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.4." + tag, new Byte[1], datatypes.str)); //Untagged Members
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.5." + tag, 4, datatypes.integer)); //Create and Go
return sendSNMP(request);
}
public bool deleteVlan(int tag)
{
try
{
return sendSNMP("1.3.6.1.2.1.17.7.1.4.3.1.5." + tag, 6);
}
catch (Exception)
{
//return false;
throw;
}
}
public bool addPortToVlan(int tag, int ifIndex, bool isTagged)
{
try
{
Byte[] currentMembers;
if (GetQBridgeVlanMembers(tag, isTagged, out currentMembers) == false)
return false;
if (currentMembers.Length == 0)
return false; //vlan needs to be created first
currentMembers = this.GeneratePortByteStream(ifIndex, currentMembers, true);
//Due to bug in TI code
//Issue is that QBridge Add port to vlan logic does not check if target port is tagged
//So if target port is already member of another vlan as tagged it will fail on all other adds
//Workaround is to use 1.3.6.1.4.1.1991.1.1.3.2.6.1.3.. i 4 to add the port to the vlan after it is added via qbridge to first
//If we are doing tagged we need to do workaround
if (isTagged)
{
//check to see if target port is already member of another vlan
if (isPortTaggedinAnotherVlan(ifIndex) == true)//add with private OID
return sendSNMP("1.3.6.1.4.1.1991.1.1.3.2.6.1.3." + tag + "." + ifIndex, 4);
//if this did not find and return fall through to qbridge
}
return SetQBridgeVlanMembers(currentMembers, tag, isTagged);
}
catch (Exception e)
{
// return false;
throw e;
}
}
public bool removePortFromVlan(int tag, int ifIndex)
{
try
{
return sendSNMP("1.3.6.1.4.1.1991.1.1.3.2.6.1.3." + tag + "." + ifIndex, 3);
}
catch (Exception e)
{
//return false;
throw e;
}
}
public bool? isPortInVlan(int tag, int ifIndex, bool tagged)
{
Raw_VlanMemberCollection members = GetVlanMembers(tag);
if (members.isErrorState == true)
return null;
if (members.Find(v => v.PortifIndex == ifIndex && v.isTagged == tagged) != null)
return true;
else
return false;
}
public VlanCollection getVlans()
{
VlanCollection Vlans = new VlanCollection();
SNMPDataCollection data = walk("1.3.6.1.4.1.1991.1.1.3.2.7.1.1");
if (data.isErrorState == true)
{
Vlans.isErrorState = true;
return Vlans;
}
foreach (SNMPData obj in data)
{
string vlanTag = obj.value.ToString(); //each value will be a vlan
string oid = obj.oid;
int ifIndex = Convert.ToInt32(vlanTag); //Foundry uses the Tag as the ifIndex
//Will include 4095 which is the Management Vlan
Vlan vlan = new Vlan();
vlan.tag = Convert.ToInt32(vlanTag);
vlan.ifIndex = Convert.ToInt32(ifIndex);
vlan.name = getVlanName(vlan.tag);
Vlans.Add(vlan);
}
return Vlans;
}
public Raw_VlanMemberCollection GetVlanMembers(int tag)
{
Raw_VlanMemberCollection data = new Raw_VlanMemberCollection();
//Get untagged port members
byte[] vlanMembers;
if (GetQBridgeVlanMembers(tag, false, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
ArrayList members = GetMemberPorts(vlanMembers);
foreach (int p in members)
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = false;
member.tag = tag;
member.port = p;
member.slot = 1; //only supports 1 slot
member.PortifIndex = p;
member.VlanifIndex = tag;
data.Add(member);
}
//Get Tagged members
if (GetQBridgeVlanMembers(tag, true, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
members = GetMemberPorts(vlanMembers);
foreach (int p in members)
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = true;
member.tag = tag;
member.port = p;
member.slot = 1; //only supports 1 slot
member.PortifIndex = p;
member.VlanifIndex = tag;
data.Add(member);
}
return data;
}
public NetworkPortCollection getPorts()
{
NetworkPortCollection Ports = new NetworkPortCollection();
SNMPDataCollection data = walk("1.3.6.1.4.1.1991.1.1.3.3.5.1.18");
if (data.isErrorState == true)
{
Ports.isErrorState = true;
return Ports;
}
foreach (SNMPData obj in data) //Get all our ports
{
string value = obj.value.ToString();
//static devices dont return slot info
if (value.IndexOf(@"/") == -1) //we are not doing a moduler device
{
value = @"1/" + value; //set default slot to 1
}
//Value returned looks like /
//Lets split it out
string[] slotport = value.Split(Convert.ToChar(@"/"));
int id = Convert.ToInt32(obj.oid.ToString().Substring(obj.oid.ToString().LastIndexOf(".") + 1));
NetworkPort port = new NetworkPort(id, Convert.ToInt32(slotport[0]), Convert.ToInt32(slotport[1]));
port.name = getPortDescription(port.ifIndex);
port.adminStatus = getPortAdminStatus(port.ifIndex);
port.operationStatus = getPortOperationStatus(port.ifIndex);
if (port.unTaggedVlan == null)
{
port.unTaggedVlan = new VlanMember();
}
port.unTaggedVlan.tag = getPortUntaggedVlan(port.ifIndex);
Ports.Add(port);
}
return Ports;
}
public bool addFirstPorttoVlan(int tag, int ifIndex, bool isTagged, string vlanName)
{
try
{
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.1." + tag, vlanName, SNMPBase.datatypes.str)); //Vlan Name
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.2." + tag, new byte[1], SNMPBase.datatypes.str)); //Egress Ports
if (isTagged)
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.4." + tag, new byte[1], SNMPBase.datatypes.str)); //Untagged ports
request.Add(new SNMPData("1.3.6.1.2.1.17.7.1.4.3.1.5." + tag, 4, SNMPBase.datatypes.integer)); //CreateAndGo
bool response = sendSNMP(request);
return addPortToVlan(tag, ifIndex, isTagged);
}
catch (Exception)
{
return false;
throw;
}
}
private int getPortUntaggedVlan(int ifIndex)
{
string data;
if (getSNMP("1.3.6.1.4.1.1991.1.1.3.3.5.1.24." + ifIndex.ToString(), SNMPBase.datatypes.integer, out data) == false)
return -1;
return Convert.ToInt32(data);
}
#region "Private"
private bool isPortTaggedinAnotherVlan(int ifIndex)
{
VlanCollection vlans = getVlans();
foreach (Vlan vlan in vlans)
{
Raw_VlanMemberCollection members = GetVlanMembers(vlan.tag);
Raw_VlanMember member = members.Find(m => m.PortifIndex == ifIndex && m.isTagged == true);
if (member != null)
return true; //found a member
}
return false; //port is not any other vlans
}
private static readonly Byte[] PORTMASKARRAY = { 128, 64, 32, 16, 8, 4, 2, 1 };
private ArrayList GetMemberPorts(Byte[] memberbytes)
{
//Find out which bit positions are set in a byte. Based off which position and byte we are in we can determine the port number
//ie bit 7 in byte 0 = port 1
//ie bit 0 in byte 0 = port 8
ArrayList members = new ArrayList();
int bytecoute = 0;
int portNumber = 0;
int result = 0;
foreach (Byte b in memberbytes)
{
if (memberbytes[bytecoute] == 0)
{
bytecoute++; // No ports where active in the Byte
}
else // if we have port membership in the Byte lets see which ports
{
for (int i = 0; i < 8; i++) // Loop through each bit
{
result = memberbytes[bytecoute] & PORTMASKARRAY[i]; // Is each bit value (port) in the array?
if (result == PORTMASKARRAY[i])
{
portNumber = i + 1 + bytecoute * 8;
members.Add(portNumber); // Add the portnumber to our returned list
}
}
bytecoute++;
}
}
return members;
}
private static bool isPortMember(int portnumber, Byte[] membershipstream)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
portnumber %= 1000;
//Byte[] PORTMASKARRAY = { 128, 64, 32, 16, 8, 4, 2, 1 };
return (membershipstream[(portnumber - 1) / 8] & PORTMASKARRAY[(portnumber - 1) % 8]) != 0;
}
private bool GetQBridgeVlanMembers(int tag, bool isTagged, out Byte[] members)
{
members = new Byte[0];
if (isTagged)
{
byte[] allmembers;
getSNMP("1.3.6.1.2.1.17.7.1.4.2.1.4.0." + tag, out allmembers); //dot1qVlanStaticEgressPorts
//dot1qVlanStaticEgressPorts also includes untagged ports. So we have to remove the untagged ports from the array.
//What is left will be the tagged only ports
byte[] untaggedMembers;
if (GetQBridgeVlanMembers(tag, false, out untaggedMembers) == false)
return false;
byte[] taggedports = new byte[allmembers.Length];
for (int i = 0; i < allmembers.Length; i++)
{
taggedports[i] = Convert.ToByte(allmembers[i] ^ untaggedMembers[i]);
}
members = taggedports;
return true;
}
else
{
byte[] untaggedMembers;
if (getSNMP("1.3.6.1.2.1.17.7.1.4.2.1.5.0." + tag, out untaggedMembers) == false) //dot1qVlanStaticUntaggedPorts
return false;
members = untaggedMembers;
return true;
}
}
private bool SetQBridgeVlanMembers(Byte[] members, int tag, bool isTagged)
{
//Allways add Vlan member to Egress port. By default is tagged. If you want untagged then also add to untaggedPorts
bool result = false;
if (isTagged)
{
return sendSNMP("1.3.6.1.2.1.17.7.1.4.3.1.2." + tag, members, datatypes.str); //dot1qVlanStaticEgressPorts
}
else
{
//for egreess ports we need to get all current egress ports to append the current port
Byte[] currentMembers;
if (GetQBridgeVlanMembers(tag, true, out currentMembers) == false)
return false;
if (currentMembers.Length == 0)
return false; //vlan needs to be created first
result = SetQBridgeVlanMembers(currentMembers, tag, true);
if (result == false)
return false;
result = sendSNMP("1.3.6.1.2.1.17.7.1.4.3.1.4." + tag, members, datatypes.str); //dot1qVlanStaticUntaggedPorts
}
return result;
}
///
/// QBridge Byte Stream generation, used for modifing port membership in Vlan
///
/// portnumber to add/remove from vlan
///
private Byte[] GeneratePortByteStream(int newMemberPort, Byte[] currentMembers, bool addPort)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
//portnumber %= 1000;
Byte[] holdingByte = new Byte[currentMembers.Length];
Array.Copy(currentMembers, holdingByte, currentMembers.Length);
//Determin which byte we are working with
int byteposition = (newMemberPort - 1) / 8;
//Mod to find the bit we are working with
int maskindex = (newMemberPort - 1) % 8;
//Set the value in our Holding array for the corresponding bit
if (addPort)
holdingByte[byteposition] |= PORTMASKARRAY[maskindex];
else
{
if ((holdingByte[byteposition] & PORTMASKARRAY[maskindex]) == 0) //check to see if the newMemberport is a member of the currentMembers
return holdingByte;
if (currentMembers[byteposition] == 0) //dont xor a null value. If the port is not already a member dont try to remove it.
return holdingByte;
// holdingByte[byteposition] = Convert.ToByte(currentMembers[byteposition] & INVERTED_PORTMASKARRAY[maskindex]);
holdingByte[byteposition] = Convert.ToByte(currentMembers[byteposition] ^ PORTMASKARRAY[maskindex]);
}
return holdingByte;
}
private string ConvertToHex(object bytearray)
{
byte[] ba = (byte[])bytearray;
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
#endregion
Post 4: Green Monster Network plugin examples
Part of the Green Monster Project is to
abstract the hardware for Vlan Management.
To do this I created a “plugin” for each network switch that we use at
the EEC. When action needs to be taken
against a network switch. Green Monster
first queries the database (See post 2)
to determine the DLL that it should load into the plugin manager. Once it is loaded I can execute the exposed
methods.
This plugin model is the same process used for Power
management and KVM management. The
interaction with the network switch is abstracted from the main code. It is up to the plugin to know the best ways
to execute a given command. This allows
for the plugin to use any available execution methods. This can include SNMP, Netconf, scripted
telnet, SSH, or other web services. As
long as the plugin has the ability to communicate via the protocol desired it
should work.
The majority of the code we use for device interaction is
SNMP. So I thought I would post how we
use SNMP to do Vlan management. Some of
this was found by doing packet sniffs and working with the Manufactures
engineers.
For SNMP in CSharp I use the nsoftware.com SSNMP
library. It is very fast and we had luck with it working well. The code to
make the “raw” snmp call is not included because that requires a license for
the nsoftware library. If you have that
license and are interested please let me know and I can share some snip-its.
Brocade Networks (Turborion, MLX)
Post 3: Green Monster Plugin system
Part 3 of N. See Part 2 or Part 1
The plugin system allowed for 3 “device types” that I could automate. KVM Switch, Network Switch and Power Distribution unit (PDU). I created an interface for each that the plugin had to implement to work with Green Monster.
Green Monster Plugin design
One of Green Monster’s primary goals was to provide a
framework to automate tasks against Network Switches, KVM Switches, Rack PDUs,
Blade Chassis Management Interfaces, iLO/DRAC.
By abstracting the network design and underling hardware my team members
do not need to know how all the network switches interconnect or how to use the
CLI/GUI for each manufacture/model.
I created a plugin framework where each model references a
DLL file that is to be loaded when action against it is to be taken. This Plugin Name field (see Green Monster
Database design post) exists on the Model level and the asset attribute level. This allows for a specific (maybe version
specific) dll to be used on a single asset or a version agnostic DLL to be used
on all common models.
When a device action
is being performed Green Monster will run the get plugin sproc that first
checks to see if the “asset” as a custom plugin. If not it looks up the plugin for the
model. This seemed to work well when a
single device had a different version of software (say Switch firmware) that
broke with the Model default plugin. It
also allowed a way to prototype a plugin without rolling it out to all devices.
The plugin system allowed for 3 “device types” that I could automate. KVM Switch, Network Switch and Power Distribution unit (PDU). I created an interface for each that the plugin had to implement to work with Green Monster.
I will mostly focus
on the network devices because they were one of the main goals with Green
Monster.
SNMP was the most used protocol when working with devices. Though there is nothing in the system that required it to be. I wrote a power plugin for the HB C-7000 blade chassis OA that utilized scripted telnet. While it was slow it worked well where the manufactures had lacking APIs/interfaces for automating their devices.
Post 2: Green Monster DB Info
The key to Green Monster is the database. The database holds information about “assets”
which are any physical item that should be inventoried or will be used for
automation. For the EEC we inventory
Servers, Laptops, desktops, Rack PDUs, KVM Switches, Network Switches, HP OA
interfaces, Blade Chassis, SAN storage, PCIe Flash Storage (Fusion IO), Load
Balancers (F5, Brocade), Encryption devices (nCypher HSM), Racks, Blades, DAS
shelves (MSA 70), and many other things.
The database has tables for Manufactures, Models, Device
Type (Network Switch, KVM switch, other items from above). An “asset” links to a Model which then links
to Device Type and Manufacture.
99% of Assets will have common attributes (though with
different values), for example Serial Number, warranty start Date, Cost. Some additional values that we find most of
our Assets have include parent Asset (for child parent relationship), status code, Purchase Order number, Asset tag
(inventory tracking).
CPU ID from above links to a table which tracks information about what CPUs exit in a Server. This allows us to track usage of CPU features tested and also find systems that meet specific customer requirements for a scenario.
The Models table which has a direct relationship to the
Assets table as the following fields, Model Name, Model Number, Code Name, Size
in RU, Weight, Power Voltage, Power Current, and like the attributes table
there is a plugin name field (more on this field in the following post).
When the EEC does performance or scale testing on equipment
it is important to know the exact specifications of all the systems that were
used in the test. These systems can be
all the same model or a mix of model, manufacture, architecture (Intel,
AMD). Over the life cycle of a server we
will upgrade the CPUs to faster (maybe pre-release) options, or change the
memory speed/quantity. Even with these changes
we need to be track what the exact specs were at any given point in time. The solution to that issue is an Audit
table. The Audit table is populated from
triggers that exist on the Asset, attribute and CPU tables. It tracks current and previous values anytime
a field is changed in one of the monitored tables.
With these tables and a few others we have a DB design for
basic inventory tracking that offers a child/parent relationship, tracks
information about specific systems, provides a history of a Servers
upgrade/downgrade and will scale very well.
For the automation pieces of Green Monster to work it relies on knowing how each component (Network, Server, NIC, Power Port, KVM) all relate to each other.For Network and Power control each asset has a 1 to many relationship. This means that a Server can have >1 network port and >1 Power ports.
Each port has a “parent”
of the asset and a neighbor of where it connects to. If a Server has 2 nics it will have 2 entries
in the network ports table. If a switch
has 48 ports it will have 48 entries in the table. From that a simple mapping from server nic 1
to switch port 1 can be done. This
allows the automation pieces to know how everything relates.
For network ports
the MAC address of the NIC is also tracked.
This is used for assigning IP address during the OS image phase and for
the “audit” tool to make sure that a Mac address is showing on the correct
Network switch port when dumping the MAC table.
More on those later.
Using C# to manage an Extreme Extremeware based switch via SNMP
This is to provide an example of a plug-in I wrote for the Green Monster System to manage an Extreme Networks ExtremeWare based switch. This is there older line of switches
Most of these commands were determined via many hours with network sniffer as the MIBs do not expose the OIDs used here.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
Most of these commands were determined via many hours with network sniffer as the MIBs do not expose the OIDs used here.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
public bool createVlan(int tag, string name)
{
//1.3.6.1.4.1.1916.1.2.1.2.1.1.22016 i 22016
//1.3.6.1.4.1.1916.1.2.1.2.1.2.22016 s testvlan
//1.3.6.1.4.1.1916.1.2.1.2.1.3.22016 i 1
//1.3.6.1.4.1.1916.1.2.1.2.1.4.22016 i 22016
//1.3.6.1.4.1.1916.1.2.1.2.1.6.22016 i 4
//Get new Index
//1.3.6.1.4.1.1916.1.2.3.1.1.2.22018 i 1
//1.3.6.1.4.1.1916.1.2.3.1.1.3.22018 i 124
//1.3.6.1.4.1.1916.1.2.3.1.1.4.22018 i 4
//1.3.6.1.2.1.31.1.2.1.1.22016.22018 i 22016
//1.3.6.1.2.1.31.1.2.1.2.22016.22018 i 22018
//1.3.6.1.2.1.31.1.2.1.3.22016.22018 i 4
//Query to get the next available index for this switch
int index1 = getAvailableIndex();
if (index1 == -1)
return false;
bool results = false;
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.1." + index1, index1, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.2." + index1, name, SNMPBase.datatypes.str));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.3." + index1, 1, SNMPBase.datatypes.integer)); //Vlan Type
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.4." + index1, index1, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.6." + index1, 4, SNMPBase.datatypes.integer)); //Vlan status
results = sendSNMP(request);
if (results == false)
return false;
int index2 = getAvailableIndex();
if (index2 == -1)
return false;
request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.3.1.1.2." + index2, 1, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.3.1.1.3." + index2, tag, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.3.1.1.4." + index2, 4, SNMPBase.datatypes.integer));
results = sendSNMP(request);
if (results == false)
return false;
request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.2.1.31.1.2.1.1." + index1 + "." + index2, index1, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.2.1.31.1.2.1.2." + index1 + "." + index2, index2, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.2.1.31.1.2.1.3." + index1 + "." + index2, 4, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool deleteVlan(int tag)
{
int untaggedindex = getVlanIndexID(tag, false);
int taggedindex = getVlanIndexID(tag, true);
if (untaggedindex == -1 || taggedindex == -1)
return false;
bool result = false;
//remove the untagged to tagged mapping
result = sendSNMP("1.3.6.1.2.1.31.1.2.1.3." + untaggedindex + "." + taggedindex, 6);
if (result == false)
{
return false;
}
result = sendSNMP("1.3.6.1.4.1.1916.1.2.3.1.1.4." + taggedindex, 6);
if (result == false)
{
return false;
}
result = sendSNMP("1.3.6.1.4.1.1916.1.2.1.2.1.6." + untaggedindex, 6);
if (result == false)
{
return false;
}
return true;
}
public bool addPortToVlan(int tag, int ifIndex, bool isTagged)
{
//get the slot and port info for the ifIndex
//string slotdata = getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str);
//int slot = Convert.ToInt32(slotdata.Substring(0, slotdata.IndexOf("/")));
//int port = Convert.ToInt32(slotdata.Substring(slotdata.IndexOf("/") + 1));
//Get the Vlan's Index ID
int vlanIndex = getVlanIndexID(tag, isTagged);
if (vlanIndex == -1)
return false;
//int tagged;
//if (isTagged)
//{
// tagged = 1;
//}
//else
//{
// tagged = 2;
//}
//1.3.6.1.4.1.1916.1.6.3.0 i 0
//1.3.6.1.2.1.31.1.2.1.3.22015.1002 i 4
//undofailed = port already in another vlan
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.6.3.0", 0, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.2.1.31.1.2.1.3." + vlanIndex.ToString() + "." + ifIndex, 4));
return sendSNMP(request);
}
public bool addFirstPorttoVlan(int tag, int ifIndex, bool isTagged, string vlanName)
{
//Extreme allows empty vlans so create the vlan then add the ports
bool response = false;
response = createVlan(tag, vlanName);
if (response == false)
{
return false;
}
//add the port
response = addPortToVlan(tag, ifIndex, isTagged);
if (response == false)
{
return false;
}
return true;
}
public bool removePortFromVlan(int tag, int ifIndex)
{
//Get the Vlan's Index ID
int vlanIndex = getVlanIndexID(tag, false);
if (vlanIndex == -1)
return false;
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.6.3.0", 0, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.2.1.31.1.2.1.3." + vlanIndex.ToString() + "." + ifIndex, 6, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool? isPortInVlan(int tag, int ifIndex, bool tagged)
{
string slotdata;
if (getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str, out slotdata) == false)
return null;
int slot = Convert.ToInt32(slotdata.Substring(0, slotdata.IndexOf("\\")));
int port = Convert.ToInt32(slotdata.Substring(slotdata.IndexOf("\\") + 1));
Byte[] vlanMembers;
if (getVlanMembers(tag, slot, tagged, out vlanMembers) == false)
return null;
return isPortMember(port, vlanMembers);
}
public Raw_VlanMemberCollection GetVlanMembers(int tag)
{
Raw_VlanMemberCollection data = new Raw_VlanMemberCollection();
//get the number of slots
//int slots = Convert.ToInt32(getSNMP("1.3.6.1.4.1.1916.1.1.2.1.0", SNMPBase.datatypes.integer));
NetworkPortCollection Ports = getPorts(false);
int slots = GetNumberofSlots(Ports);
// we have to process vlan memberships for each slot
for (int slot = 1; slot <= slots; slot++)
{
//Get Tagged vlan members for slot
Byte[] vlanMembers;
if (getVlanMembers(tag, slot, true, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
ArrayList members = GetMemberPorts(vlanMembers); //Get a list of each port number based on mask
foreach (int p in members)
{
NetworkPort port = Ports.Find(o => o.slotNumber == slot && o.portNumber == p);
if (port != null) //we found our port lets update the membership
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = true;
member.tag = tag;
member.port = port.portNumber;
member.slot = port.slotNumber;
member.PortifIndex = port.ifIndex;
data.Add(member);
}
}
//Get un-Tagged vlan members for slot
if (getVlanMembers(tag, slot, false, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
members = GetMemberPorts(vlanMembers); //Get a list of each port number based on mask
//Ports = getPorts();
foreach (int p in members)
{
NetworkPort port = Ports.Find(o => o.slotNumber == slot && o.portNumber == p);
if (port != null) //we found our port lets update the membership
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = false;
member.tag = tag;
member.port = port.portNumber;
member.slot = port.slotNumber;
member.PortifIndex = port.ifIndex;
data.Add(member);
}
}
}
return data;
}
private int GetNumberofSlots(NetworkPortCollection ports)
{
int maxslot = 1; // start off with 1 slot
foreach (NetworkPort p in ports)
{
if (p.slotNumber > maxslot)
maxslot = p.slotNumber;
}
return maxslot;
}
public VlanCollection getVlans()
{
VlanCollection Vlans = new VlanCollection();
//Get All the Vlans
SNMPDataCollection data = walk("1.3.6.1.4.1.1916.1.2.1.2.1.10");
if (data.isErrorState == true)
{
Vlans.isErrorState = true;
return Vlans;
}
foreach (SNMPData obj in data)
{
string vlanTag = obj.value.ToString(); //each value will be a vlan
string oid = obj.oid;
int ifIndex = Convert.ToInt32(oid.Replace("1.3.6.1.4.1.1916.1.2.1.2.1.10.", string.Empty));
//Will include 4095 which is the Management Vlan
Vlan vlan = new Vlan();
vlan.tag = Convert.ToInt32(vlanTag);
vlan.ifIndex = Convert.ToInt32(ifIndex);
vlan.name = getVlanName(vlan.tag);
Vlans.Add(vlan);
}
return Vlans;
}
public NetworkPortCollection getPorts()
{
return getPorts(false);
}
public NetworkPortCollection getPorts(bool getPortStatus)
{
NetworkPortCollection Ports = new NetworkPortCollection();
//Populate ports
SNMPDataCollection data = walk("1.3.6.1.2.1.31.1.1.1.17");
if (data.isErrorState == true)
{
Ports.isErrorState = true;
return Ports;
}
foreach (SNMPData obj in data)
{
//the port enumeration also returns VLan ifIndex and Mgmt port IfIndex
//We can use 1.3.6.1.2.1.31.1.1.1.17 to see if a connector is present (is it a real port)
if (obj.value.ToString() == "1") //connector is present
{
int ifIndex = Convert.ToInt32(obj.oid.ToString().Substring(obj.oid.ToString().LastIndexOf(".") + 1));
int port = ifIndex % 1000;
int slot = (ifIndex - port) / 1000;
if (slot == 0) // if it was slot 0 set it to slot 1
{
slot = 1;
}
//Get the ports description
string description;
getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str, out description);
if (description.ToLower() != "mgmt" && description.ToLower() != "management" && description.ToLower() != "management port") //we dont want to add the management ports
{
Ports.Add(new NetworkPort(ifIndex, slot, port));
}
}
}
//Get Ports information
foreach (NetworkPort port in Ports)
{
port.name = getPortDescription(port.ifIndex);
if (getPortStatus)
{
port.adminStatus = getPortAdminStatus(port.ifIndex);
port.operationStatus = getPortOperationStatus(port.ifIndex);
}
}
return Ports;
}
///
/// Query switch for member array of a given Vlan, slot and tag
///
///
///
///
/// Extreme Vlan member array
private bool getVlanMembers(int tag, int slot, bool isTagged, out Byte[] members)
{
members = new Byte[0];
//Get the Vlan's Index ID
int vlanIndex = getVlanIndexID(tag, false);
if (vlanIndex == -1)
return true; //Vlan was not found on switch
int tagged;
if (isTagged)
tagged = 1;
else
tagged = 2;
return getSNMP("1.3.6.1.4.1.1916.1.2.6.1.1." + tagged + "." + vlanIndex + "." + slot, out members);
}
///
/// Process Byte Array to determine member ports
///
///
/// Returns an array for port numbers for a MemberByteArray
private ArrayList GetMemberPorts(Byte[] memberbytes)
{
//Find out which bit positions are set in a byte. Based off which position and byte we are in we can determine the port number
//ie bit 7 in byte 0 = port 1
//ie bit 0 in byte 0 = port 8
ArrayList members = new ArrayList();
int bytecoute = 0;
int portNumber = 0;
int result = 0;
foreach (Byte b in memberbytes)
{
if (memberbytes[bytecoute] == 0)
{
bytecoute++; //No ports where active in the Byte
}
else //if we have port membership in the Byte lets see which ports
{
for (int i = 0; i < 8; i++)//Loop through each bit
{
result = memberbytes[bytecoute] & PORTMASKARRAY[i]; //Is each bit value (port) in the array?
if (result == PORTMASKARRAY[i])
{
portNumber = i + 1 + bytecoute * 8;
members.Add(portNumber); //Add the portnumber to our returned list
}
}
bytecoute++;
}
}
return members;
}
private int getVlanIndexID(int tag, bool tagged)
{
int untaggedindex = -1;
int taggedindex = -1;
//walk to get the vlans
SNMPDataCollection data = walk("1.3.6.1.4.1.1916.1.2.1.2.1.10");
if (data.isErrorState == true)
return -1;
foreach (SNMPData item in data)
{
if (Convert.ToInt32(item.value) == tag)
{
untaggedindex = Convert.ToInt32(item.oid.Replace("1.3.6.1.4.1.1916.1.2.1.2.1.10.", ""));
break;
}
}
if (untaggedindex == -1)
return -1;
data = walk("1.3.6.1.4.1.1916.1.2.7.1.1.2." + untaggedindex);
if (data.isErrorState == true)
return -1;
foreach (SNMPData item in data)
{
taggedindex = Convert.ToInt32(item.value);
break;
}
if (tagged)
return taggedindex;
else
return untaggedindex;
}
private static bool isPortMember(int portnumber, Byte[] membershipstream)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
portnumber %= 1000;
Byte[] PORTMASKARRAY = { 128, 64, 32, 16, 8, 4, 2, 1 };
return (membershipstream[(portnumber - 1) / 8] & PORTMASKARRAY[(portnumber - 1) % 8]) != 0;
}
///
/// Extreme Byte Stream generation, used for modifing port membership in Vlan
///
/// portnumber to add/remove from vlan
///
private Byte[] ReneratePortByteStream(int portnumber, int MAXPORTSPERSLOT)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
//portnumber %= 1000;
Byte[] holdingByte = null;
//Create an Array to hold the changing value
int bytesNeeded = MAXPORTSPERSLOT / 8 + (MAXPORTSPERSLOT % 8 <= 0 ? 0 : 1);
holdingByte = new byte[bytesNeeded];
//Determin which byte we are working with
int byteposition = portnumber / 8;
//Mod to find the bit we are working with
int maskindex = (portnumber - 1) % 8;
//Set the value in our Holding array for the corisponding bit
holdingByte[byteposition] |= PORTMASKARRAY[maskindex];
return holdingByte;
}
private int getAvailableIndex()
{
string data;
if (getSNMP("1.3.6.1.4.1.1916.1.2.2.1.0", SNMPBase.datatypes.integer, out data) == false)
return -1;
return Convert.ToInt32(data);
}
private string ConvertToHex(object bytearray)
{
byte[] ba = (byte[])bytearray;
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
Using C# to manage a HP GBe2c_1_10g switch via SNMP
This is to provide an example of a plug-in I wrote for the Green Monster System to manage a HP GBe2c_1_10g
. This is not the exact same as the HP_GBe2c but it is close.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
. This is not the exact same as the HP_GBe2c but it is close.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
public bool createVlan(int tag, string name)
{
//Switch supports empty vlan
//Create the vlan with tag and name
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.2." + tag, name);
//Set the vlan as enabled
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.4." + tag, 2);
return SaveConfig();
}
public bool deleteVlan(int tag)
{
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.7." + tag, 2);
return SaveConfig();
}
public bool addPortToVlan(int tag, int ifIndex, bool isTagged)
{
//if the port is to be tagged to need to set the port to tagged mode
if (isTagged)
{
setPortTagMode(ifIndex, isTagged);
}
else //untagged membership
{
setPortTagMode(ifIndex, false);
//Setting an untagged port will auto remove from other vlans and set default/native membership
}
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.5." + tag, ifIndex);
return SaveConfig();
}
public bool removePortFromVlan(int tag, int ifIndex)
{
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.6." + tag, ifIndex);
return SaveConfig();
}
public bool addFirstPorttoVlan(int tag, int ifIndex, bool isTagged, string vlanName)
{
//allows empty vlans so create the vlan then add the ports
bool response = false;
response = createVlan(tag, vlanName);
if (response == false)
{
return false;
}
//add the port
response = addPortToVlan(tag, ifIndex, isTagged);
if (response == false)
{
return false;
}
return true;
}
public bool? isPortInVlan(int tag, int ifIndex, bool tagged)
{
Raw_VlanMemberCollection members = GetVlanMembers(tag);
if (members.isErrorState == true)
return null;
Raw_VlanMember member = members.Find(m => m.PortifIndex == ifIndex);
if (member == null)
return false;
//make sure tagging is correct
if (member.isTagged == tagged)
return true; //tagging matched
else
return false;
}
public Raw_VlanMemberCollection GetVlanMembers(int tag)
{
//The port list in the VLAN. The ports are presented in bitmap format.
//in receiving order:
//OCTET 1 OCTET 2 .....
//xxxxxxxx xxxxxxxx .....
//|||||_ port 8
//||||
//||||___ port 7
//|||____ port 6
//||. . .
//||_________ port 1
//|__________ reserved
//where x :1 - The represented port belongs to the VLAN
//0 - The represented port does not belong to the VLAN
Raw_VlanMemberCollection data = new Raw_VlanMemberCollection();
byte[] vlanMembers;
if (getSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.3.2221", out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
ArrayList members = GetMemberPorts(vlanMembers);
foreach (int p in members)
{
Raw_VlanMember member = new Raw_VlanMember();
bool? isTagged = isPortTagged(p);
if (isTagged == null)
{
data.isErrorState = true;
return data;
}
member.isTagged = (bool)isTagged;
member.tag = tag;
member.port = p;
member.slot = 1; //only supports 1 slot
member.PortifIndex = p;
member.VlanifIndex = tag;
data.Add(member);
}
return data;
}
public VlanCollection getVlans()
{
VlanCollection vlans = new VlanCollection();
SNMPDataCollection data = walk("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.2");
if (data.isErrorState == true)
{
vlans.isErrorState = true;
return vlans;
}
foreach (SNMPData obj in data)
{
Vlan vlan = new Vlan();
int tag = Convert.ToInt32(obj.oid.Replace("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.1.1.3.1.2.", ""));
string name = obj.value.ToString();
vlan.tag = tag;
vlan.name = name;
if (tag != 4095) //dont add the MGMT vlan
vlans.Add(vlan);
}
return vlans;
}
public NetworkPortCollection getPorts()
{
NetworkPortCollection Ports = new NetworkPortCollection();
//Populate ports
//Name agPortCurCfgIndx
SNMPDataCollection data = walk("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.2.2.1.1");
if (data.isErrorState == true)
{
Ports.isErrorState = true;
return Ports;
}
foreach (SNMPData obj in data)
{
//Check to see if the name of the port is XConnect or Mgmt
int ifIndex = Convert.ToInt32(obj.value);
string interfaceName;
getSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.2.2.1.15." + ifIndex, datatypes.str, out interfaceName);
interfaceName = interfaceName.ToLower();
//Xconnect ports are interlinks to switch in neighbor bay
//mgmt is management link
if (interfaceName.IndexOf("xconnect") == -1 && interfaceName.IndexOf("mgmt") == -1)
{
int slot = 0; //only has 1 slot
int port = ifIndex;
NetworkPort p = new NetworkPort();
p.ifIndex = ifIndex;
p.slotNumber = slot;
p.portNumber = port;
p.interfaceType = string.Empty;
p.name = interfaceName;
//Get the ports description
Ports.Add(p);
}
}
return Ports;
}
public portStatus getPortAdminStatus(int ifIndex)
{
//1=enabled
//2 = disabled
ifIndex = ifIndex + 256;
string data;
if (getSNMP("1.3.6.1.2.1.2.2.1.8." + ifIndex.ToString(), datatypes.str, out data) == false)
return portStatus.error;
return (portStatus)Convert.ToInt32(data) - 1;
}
public bool setPortAdminStatus(int ifIndex, portStatus status)
{
ifIndex = ifIndex + 256;
sendSNMP("1.3.6.1.2.1.2.2.1.8." + ifIndex.ToString(), Convert.ToInt32(status + 1));
return SaveConfig();
}
public portStatus getPortOperationStatus(int ifIndex)
{
//1 = link
//2 = no link
ifIndex = ifIndex + 256;
string data;
if (getSNMP("1.3.6.1.2.1.2.2.1.8." + ifIndex.ToString(), datatypes.str, out data) == false)
return portStatus.error;
return (portStatus)Convert.ToInt32(data) - 1;
}
public bool SaveConfig()
{
//apply the change
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.1.2", 2);
//save
return sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.1.4", 2);
}
public bool RebootDevice()
{
return sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.1.7", 3);
}
private bool setPortTagMode(int ifIndex, bool tagged)
{
//2=tagged
//3=untagged
int value = (tagged == true ? 2 : 3);
sendSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.1.1.2.3.1.3." + ifIndex, value);
return SaveConfig();
}
private bool? isPortTagged(int ifIndex)
{
//2=tagged
//3=untagged
string tagstate;
if (getSNMP("1.3.6.1.4.1.11.2.3.7.11.33.4.2.1.1.2.3.1.3." + ifIndex, datatypes.integer, out tagstate) == false)
return null;
if (tagstate == "2")
return true;
else
return false;
}
private Byte[] PORTMASKARRAY = { 128, 64, 32, 16, 8, 4, 2, 1 };
private ArrayList GetMemberPorts(Byte[] memberbytes)
{
//Find out which bit positions are set in a byte. Based off which position and byte we are in we can determine the port number
//ie bit 7 in byte 0 = port 1
//ie bit 0 in byte 0 = port 8
ArrayList members = new ArrayList();
int bytecoute = 0;
int portNumber = 0;
int result = 0;
foreach (Byte b in memberbytes)
{
if (memberbytes[bytecoute] == 0)
{
bytecoute++; // No ports where active in the Byte
}
else // if we have port membership in the Byte lets see which ports
{
for (int i = 0; i < 8; i++) // Loop through each bit
{
result = memberbytes[bytecoute] & PORTMASKARRAY[i]; // Is each bit value (port) in the array?
if (result == PORTMASKARRAY[i])
{
portNumber = i + bytecoute * 8;
members.Add(portNumber); // Add the portnumber to our returned list
}
}
bytecoute++;
}
}
return members;
}
public int getPortifIndexbyMAC(string MACAddress)
{
//OID for iFIndex
string oid = string.Empty;
//Replace : and . with space
MACAddress = MACAddress.Replace(":", "").Replace(".", "").Replace(" ", "").Replace("-", "");
SNMPDataCollection data = walk("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.3.2.2.1.1", true);
if (data.isErrorState == true)
return -1;
foreach (SNMPData item in data)
{
if (ConvertToHex(item.value).ToLower() == MACAddress.ToLower())
{
oid = item.oid.Replace("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.3.2.2.1.1.", "");
break;
}
}
//make sure we found an OID
if (oid == string.Empty)
return -1;
int i = -1;
//look up the fdb port number
string ifIndex;
if (getSNMP("1.3.6.1.4.1.11.2.3.7.11.33.5.2.2.3.2.2.1.3." + oid, datatypes.integer, out ifIndex) == false)
return -1;
if (ifIndex == "-1" || ifIndex == string.Empty)
return -1;
else
//Take the FDB base port number and get the ifIndex for the interface
if (Int32.TryParse(ifIndex, out i) == false)
return -1;
else
return i; //return the index
}
private string ConvertToHex(object bytearray)
{
byte[] ba = (byte[])bytearray;
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
#region ISwitchMangement Members
public Raw_FDBEntryCollection GetFDB()
{
throw new NotImplementedException();
}
#endregion
Using C# to manage an Extreme XOS based switch via SNMP
This is to provide an example of a plug-in I wrote for the Green Monster System to manage an Extreme Networks XOS based switch. This includes devices like the 350, 450 and BD10k and the like.
Most of these commands were determined via many hours with network sniffer as the MIBs do not expose the OIDs used here.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
Most of these commands were determined via many hours with network sniffer as the MIBs do not expose the OIDs used here.
The SNMP commands are performed via a base class that uses Nsoftware's SSNMP library. That class is not included because of licensing.
If you need help or have questions feel free to send me mail.
public bool createVlan(int tag, string name)
{
// Query to get the next available index for this switch
int index = getAvailableIndex();
if (index == -1)
return false;
// Set 1.3.6.1.4.1.1916.1.2.1.2.1.1.Index value = index type=integer
// Set 1.3.6.1.4.1.1916.1.2.1.2.1.2.index value = Vlan Name Type=String
// Set 3.6.1.4.1.1916.1.2.1.2.1.10.index value=vlantag Type=integer
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.1." + index, index, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.2." + index, name, SNMPBase.datatypes.str));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.1.2.1.10." + index, tag, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool deleteVlan(int tag)
{
int index = getVlanIndexID(tag);
if (index == -1)
return false;
return sendSNMP("1.3.6.1.4.1.1916.1.2.1.2.1.6." + index, 6);
}
public bool addPortToVlan(int tag, int ifIndex, bool isTagged)
{
//get the slot and port info for the ifIndex :
string slotdata;
if (getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str, out slotdata) == false)
return false;
if (slotdata.IndexOf(":") == -1)
return false;
int slot = Convert.ToInt32(slotdata.Substring(0, slotdata.IndexOf(":")));
int port = Convert.ToInt32(slotdata.Substring(slotdata.IndexOf(":") + 1));
//Get the Vlan's Index ID
int vlanIndex = getVlanIndexID(tag);
if (vlanIndex == -1)
return false;
int tagged;
if (isTagged)
tagged = 1;
else
tagged = 2;
// Create the byte array that will hold the bits for the port we are going to modify in the vlan
int maxportsperslot = getMaxPortsperSlot();
if (maxportsperslot == -1)
return false;
byte[] changearray = GeneratePortByteStream(port, maxportsperslot);
// Set 1.3.6.1.4.1.1916.1.2.6.2.1.1.. x 00000000000040 (bit mask of ports to add)
// set 1.3.6.1.4.1.1916.1.2.6.2.1.2..Type=integer Value =1 or 2 (1=tagged 2=untagged)
// Set 1.3.6.1.4.1.1916.1.2.6.2.1.3.. Type=integer Value=4
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.1." + vlanIndex.ToString() + "." + slot.ToString(), changearray, SNMPBase.datatypes.str));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.2." + vlanIndex.ToString() + "." + slot.ToString(), tagged, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.3." + vlanIndex.ToString() + "." + slot.ToString(), 4, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool addFirstPorttoVlan(int tag, int ifIndex, bool isTagged, string vlanName)
{
//Extreme allows empty vlans so create the vlan then add the ports
bool response = false;
response = createVlan(tag, vlanName);
if (response == false)
{
return false;
}
//add the port
response = addPortToVlan(tag, ifIndex, isTagged);
if (response == false)
{
return false;
}
return true;
}
public bool removePortFromVlan(int tag, int ifIndex)
{
//get the slot and port info for the ifIndex
string slotdata;
if (getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str, out slotdata) == false)
return false;
int slot = Convert.ToInt32(slotdata.Substring(0, slotdata.IndexOf(":")));
int port = Convert.ToInt32(slotdata.Substring(slotdata.IndexOf(":") + 1));
//Get the Vlan's Index ID
int vlanIndex = getVlanIndexID(tag);
if (vlanIndex == -1)
return false;
//Create the byte array that will hold the bits for the port we are going to modify in the vlan
int maxportsperslot = getMaxPortsperSlot();
if (maxportsperslot == -1)
return false;
byte[] changearray = GeneratePortByteStream(port, maxportsperslot);
//Set 1.3.6.1.4.1.1916.1.2.6.2.1.1.. x 00000000000040 (bit mask of ports to remove)
//set 1.3.6.1.4.1.1916.1.2.6.2.1.2..Type=integer Value =3
//Set 1.3.6.1.4.1.1916.1.2.6.2.1.3.. Type=integer Value=4
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.1." + vlanIndex.ToString() + "." + slot.ToString(), changearray, SNMPBase.datatypes.str));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.2." + vlanIndex.ToString() + "." + slot.ToString(), 3, SNMPBase.datatypes.integer));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.6.2.1.3." + vlanIndex.ToString() + "." + slot.ToString(), 4, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool? isPortInVlan(int tag, int ifIndex, bool tagged)
{
string slotdata;
if (getSNMP("1.3.6.1.2.1.31.1.1.1.1." + ifIndex, SNMPBase.datatypes.str, out slotdata) == false)
return null;
int slot = Convert.ToInt32(slotdata.Substring(0, slotdata.IndexOf(":") + 1));
int port = Convert.ToInt32(slotdata.Substring(slotdata.IndexOf(":") + 1));
Byte[] vlanMembers;
if (getVlanMembers(tag, slot, tagged, out vlanMembers) == false)
return null;
return isPortMember(port, vlanMembers);
}
public Raw_VlanMemberCollection GetVlanMembers(int tag)
{
Raw_VlanMemberCollection data = new Raw_VlanMemberCollection();
//get the number of slots
string getData;
if (getSNMP("1.3.6.1.4.1.1916.1.1.2.1.0", SNMPBase.datatypes.integer, out getData) == false)
{
data.isErrorState = true;
return data;
}
int slots = -1;
if (Int32.TryParse(getData, out slots) == false)
{
System.Threading.Thread.Sleep(1000);
//Failed to get valid data from switch. Try again
if (getSNMP("1.3.6.1.4.1.1916.1.1.2.1.0", SNMPBase.datatypes.integer, out getData) == false)
{
data.isErrorState = true;
return data;
}
if (Int32.TryParse(getData, out slots) == false)
{
throw new TimeoutException("Failed to contact Switch IP " + this.DeviceIP + " via SNMP, the switch might be down or busy. Try again later or contact the admin");
}
}
if (slots == -1)
throw new ArgumentOutOfRangeException("Failed to lookup correct slot information for device at IP" + this.DeviceIP);
int vlanIndex = getVlanIndexID(tag);
if (vlanIndex == -1)
{
// data.isErrorState = true;
return data;
}
NetworkPortCollection Ports = getPorts();
// we have to process vlan memberships for each slot
for (int slot = 1; slot <= slots; slot++)
{
//Get Tagged vlan members for slot
Byte[] vlanMembers;
if (getVlanMembers(tag, slot, true, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
ArrayList members = GetMemberPorts(vlanMembers); //Get a list of each port number based on mask
foreach (int p in members)
{
NetworkPort port = Ports.Find(delegate(NetworkPort o) { return o.slotNumber == slot && o.portNumber == p; });
if (port != null) //we found our port lets update the membership
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = true;
member.tag = tag;
member.port = port.portNumber;
member.slot = port.slotNumber;
member.PortifIndex = port.ifIndex;
member.VlanifIndex = vlanIndex;
data.Add(member);
}
}
//Get un-Tagged vlan members for slot
if (getVlanMembers(tag, slot, false, out vlanMembers) == false)
{
data.isErrorState = true;
return data;
}
members = GetMemberPorts(vlanMembers); //Get a list of each port number based on mask
// Ports = getPorts();
foreach (int p in members)
{
NetworkPort port = Ports.Find(delegate(NetworkPort o) { return o.slotNumber == slot && o.portNumber == p; });
if (port != null) //we found our port lets update the membership
{
Raw_VlanMember member = new Raw_VlanMember();
member.isTagged = false;
member.tag = tag;
member.port = port.portNumber;
member.slot = port.slotNumber;
member.PortifIndex = port.ifIndex;
member.VlanifIndex = vlanIndex;
data.Add(member);
}
}
}
return data;
}
public VlanCollection getVlans()
{
VlanCollection Vlans = new VlanCollection();
//Get All the Vlans
SNMPDataCollection data = walk("1.3.6.1.4.1.1916.1.2.1.2.1.10");
if (data.isErrorState == true)
{
Vlans.isErrorState = true;
return Vlans;
}
foreach (SNMPData obj in data)
{
string vlanTag = obj.value.ToString(); //each value will be a vlan
string oid = obj.oid;
int ifIndex = Convert.ToInt32(oid.Replace("1.3.6.1.4.1.1916.1.2.1.2.1.10.", string.Empty));
//Will include 4095 which is the Management Vlan
Vlan vlan = new Vlan();
vlan.tag = Convert.ToInt32(vlanTag);
vlan.ifIndex = Convert.ToInt32(ifIndex);
vlan.name = getVlanName(vlan.tag);
Vlans.Add(vlan);
}
return Vlans;
}
public bool setVlanIPAddress(int vlanTag, string ipAddress, string NetworkMask)
{
int vlanIfIndex = getVlanIndexID(vlanTag);
if (vlanIfIndex == -1)
return false; //unable to find vlan index
if (ipAddress == string.Empty)
return clearVlanIPaddress(vlanIfIndex); // doing a cleanup
if (NetworkMask == string.Empty)
return false; // dont allow a null network mask
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.4.1.1.1." + vlanIfIndex, ipAddress, SNMPBase.datatypes.ipAddress));
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.4.1.1.2." + vlanIfIndex, NetworkMask, SNMPBase.datatypes.ipAddress));
sendSNMP(request);
request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.4.1.1.3." + vlanIfIndex, 1, SNMPBase.datatypes.integer)); //activate the IP
return sendSNMP(request);
}
public string getVlanIPAddress(int vlanTag)
{
int vlanIfIndex = getVlanIndexID(vlanTag);
if (vlanIfIndex == -1)
return string.Empty; //unable to find vlan index
string result;
getSNMP("1.3.6.1.4.1.1916.1.2.4.1.1.1." + vlanIfIndex, SNMPBase.datatypes.ipAddress, out result);
return result;
}
public string getVlanNetworkMask(int vlanTag)
{
int vlanIfIndex = getVlanIndexID(vlanTag);
if (vlanIfIndex == -1)
return string.Empty; //unable to find vlan index
string result;
getSNMP("1.3.6.1.4.1.1916.1.2.4.1.1.2." + vlanIfIndex, SNMPBase.datatypes.ipAddress, out result);
return result;
}
public bool setVLanIPForward(int vlanTag, bool Enabled)
{
if (getVlanIPAddress(vlanTag) == string.Empty)
return false; //no ip Set
int vlanIfIndex = getVlanIndexID(vlanTag);
if (vlanIfIndex == -1)
return false; //unable to find vlan index
int value = Enabled == true ? 1 : 2; // 1 = enabled, 2 = disabled
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.4.1.1.4." + vlanIfIndex, value, SNMPBase.datatypes.integer));
return sendSNMP(request);
}
public bool? getVlanIPForward(int vlanTag)
{
if (getVlanIPAddress(vlanTag) == string.Empty)
return false; //no ip Set
int vlanIfIndex = getVlanIndexID(vlanTag);
if (vlanIfIndex == -1)
return false; //unable to find vlan index
string value;
if (getSNMP("1.3.6.1.4.1.1916.1.2.4.1.1.4." + vlanIfIndex, SNMPBase.datatypes.integer, out value) == false)
return null;
if (value == "2")
return false;
else
return true;
}
private bool clearVlanIPaddress(int ifIndex)
{
//check to see if a ip is set
string state;
if (getSNMP("1.3.6.1.4.1.1916.1.2.4.1.1.3." + ifIndex, SNMPBase.datatypes.integer, out state) == false)
return false;
if (state == string.Empty) //no ip set
return true;
SNMPDataCollection request = new SNMPDataCollection();
request.Add(new SNMPData("1.3.6.1.4.1.1916.1.2.4.1.1.3." + ifIndex, 6, SNMPBase.datatypes.integer)); //delete the IP Address
return sendSNMP(request);
}
#region "internal methods"
///
/// Query switch for member array of a given Vlan, slot and tag
///
///
///
///
/// Extreme Vlan member array
private bool getVlanMembers(int tag, int slot, bool isTagged, out Byte[] members)
{
//Get the Vlan's Index ID
members = new Byte[0];
int vlanIndex = getVlanIndexID(tag);
if (vlanIndex == -1)
return false;
int tagged;
if (isTagged)
tagged = 1;
else
tagged = 2;
return getSNMP("1.3.6.1.4.1.1916.1.2.6.1.1." + tagged + "." + vlanIndex + "." + slot, out members);
}
///
/// Process Byte Array to determine member ports
///
///
/// Returns an array for port numbers for a MemberByteArray
private ArrayList GetMemberPorts(Byte[] memberbytes)
{
//Find out which bit positions are set in a byte. Based off which position and byte we are in we can determine the port number
//ie bit 7 in byte 0 = port 1
//ie bit 0 in byte 0 = port 8
ArrayList members = new ArrayList();
int bytecoute = 0;
int portNumber = 0;
int result = 0;
foreach (Byte b in memberbytes)
{
if (memberbytes[bytecoute] == 0)
{
bytecoute++; // No ports where active in the Byte
}
else // if we have port membership in the Byte lets see which ports
{
for (int i = 0; i < 8; i++) // Loop through each bit
{
result = memberbytes[bytecoute] & PORTMASKARRAY[i]; // Is each bit value (port) in the array?
if (result == PORTMASKARRAY[i])
{
portNumber = i + 1 + bytecoute * 8;
members.Add(portNumber); // Add the portnumber to our returned list
}
}
bytecoute++;
}
}
return members;
}
private int getVlanIndexID(int tag)
{
bool triedagain = false;
tryagain: ;
//walk to get the vlans
SNMPDataCollection data = walk("1.3.6.1.4.1.1916.1.2.1.2.1.10");
if (data.isErrorState == true)
return -1;
foreach (SNMPData item in data)
{
if (Convert.ToInt32(item.value) == tag)
return Convert.ToInt32(item.oid.Replace("1.3.6.1.4.1.1916.1.2.1.2.1.10.", ""));
}
if (triedagain == false)
{
triedagain = true;
System.Threading.Thread.Sleep(100);
goto tryagain;
}
return -1; //Could not find the tag
}
private static bool isPortMember(int portnumber, Byte[] membershipstream)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
portnumber %= 1000;
Byte[] PORTMASKARRAY = { 128, 64, 32, 16, 8, 4, 2, 1 };
return (membershipstream[(portnumber - 1) / 8] & PORTMASKARRAY[(portnumber - 1) % 8]) != 0;
}
///
/// Extreme Byte Stream generation, used for modifing port membership in Vlan
///
/// portnumber to add/remove from vlan
///
private Byte[] GeneratePortByteStream(int portnumber, int MAXPORTSPERSLOT)
{
//Determine the port number we are working with for the given slot
//Mod by 1000 to remove slot number; set remainder to portnumber
//portnumber %= 1000;
Byte[] holdingByte = null;
//Create an Array to hold the changing value
int bytesNeeded = MAXPORTSPERSLOT / 8 + (MAXPORTSPERSLOT % 8 <= 0 ? 0 : 1);
holdingByte = new byte[bytesNeeded];
//Determin which byte we are working with
int byteposition = (portnumber - 1) / 8;
//Mod to find the bit we are working with
int maskindex = (portnumber - 1) % 8;
//Set the value in our Holding array for the corisponding bit
holdingByte[byteposition] |= PORTMASKARRAY[maskindex];
return holdingByte;
}
private int getMaxPortsperSlot()
{
//Get Max ports per Slot
string data;
if (getSNMP("1.3.6.1.4.1.1916.1.1.2.3.0", SNMPBase.datatypes.integer, out data) == false)
return -1;
return Convert.ToInt32(data);
}
private int getAvailableIndex()
{
string data;
if (getSNMP("1.3.6.1.4.1.1916.1.2.2.1.0", SNMPBase.datatypes.integer, out data) == false)
return -1;
return Convert.ToInt32(data);
}
private string ConvertToHex(object bytearray)
{
byte[] ba = (byte[])bytearray;
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
#endregion
}
}
Post 1: Building a cross platform network switch automation system
Note: I have had this info in a pending publish state for a while and decided to finally post it.
As the EEC started its major remodel in 2008 we viewed this as a great time to improve our systems and processes.
In 2008 I started work on a project codenamed Green Monster (GM). The goals of Green Monster were
1. Reduce the manual process required to perform network vlan and Layer 3 changes by creating automation framework
2. Support a diverse list of equipment manufacturers (network, power, kvm, servers)
3. Create a database of equipment inventory to replace excel spreadsheets
4. Create a database of network and power port to server mappings
5. Provide an automated system to power systems on/off
6. Create a near zero touch OS deployment environment for Engagement build out/setup
7. Abstract hardware vendor and network design from users (UI or script interface)
When I was a team member at the Enterprise Engineer Center (http://www.microsoft.com/en-us/eec/default.aspx) we use a
mix of network equipment (and Power PDUs, KVM, Servers and SAN). The EEC hosts
customer “engagements” to test/validate scale, proof of concept and
features. The facility has 7 customer
labs and security is a number 1 priority. The EEC has multiple levels of security
to isolate customer environments from each other, our team systems, and the
Microsoft corporate network. Vlans are used extensively as part of the isolation system and as part of testing customer scenarios. Some test environments may only have 1 Vlan
others may have 25. Vlans are created and deleted as required for the scenarios being tested. As “the networking guy” I was
called to action regularly to help resolve connectivity issues with Vlans that
spanned multiple switches. Many times
the issues were caused by a single interface missing its Vlan configuration (tagged or untagged). While the fix for issues was very easy
finding which interface was incorrectly configured proved to be very time
consuming.
To increase the complexity the EEC uses a mix of network
switch vendors depending on our partnership and features offered by each. We also have a large variety of server
equipment from multiple partners. These servers can be pre-release systems or a
single system that fits a custom need.
As the EEC started its major remodel in 2008 we viewed this as a great time to improve our systems and processes.
In 2008 I started work on a project codenamed Green Monster (GM). The goals of Green Monster were
2. Support a diverse list of equipment manufacturers (network, power, kvm, servers)
3. Create a database of equipment inventory to replace excel spreadsheets
4. Create a database of network and power port to server mappings
5. Provide an automated system to power systems on/off
6. Create a near zero touch OS deployment environment for Engagement build out/setup
7. Abstract hardware vendor and network design from users (UI or script interface)
Development of Green Monster
was to be done as a side project. I was
the PM, Dev and for the most part the tester.
Many of my teammates helped with feature feedback, DB design ideas and
testing. They also provided a large list of features and ideas for future
versions.
Green Monster consists of a
WCF Service that performs all the automation logic, DB interaction and device
integration. The frontend is a C# UI that consumes the WCF service. OS imaging was done with WDS and some in-house scripts written by a co-worker.
Wednesday, August 7, 2013
Migrating TFS (2010 or 2012) Personal Queries from one project to another via Powershell
The powershell script below can be saved run on a users system to migrate their personal TFS Queries from one project to another. This is useful when migrating from TFS 2010 to TFS 2012.
You will need to update the script Source and Target Server URLs, TPC and Projects. I went down the path to hard code them and not take them as arguments so users did not have to enter anything. The script will throw errors if the query being migrated is not valid on the target project.
You will need to update the script Source and Target Server URLs, TPC and Projects. I went down the path to hard code them and not take them as arguments so users did not have to enter anything. The script will throw errors if the query being migrated is not valid on the target project.
function ProcessQueryItem ($queryFolder, $parentFolder, $sourceProjectName)
{
$newItem = $null
foreach ($subQuery in $queryFolder)
{
if ($subQuery -is [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder])
{
$newItem = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder -ArgumentList @($subQuery.Name)
if (-not $parentFolder.Contains($subQuery.Name))
{
Write-Host "Adding Query Folder $subQuery"
$parentFolder.Add($newItem);
$WIStoreRight.Projects[$RightProject].QueryHierarchy.Save()
ProcessQueryItem $subQuery $newItem $sourceProjectName
}
else
{
Write-Host "Query Folder "$subQuery.name" already exists" -foregroundcolor Yellow
}
}
else
{
Write-Host "Creating query "$subQuery.Name
$oldDef = [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryDefinition]$subQuery
$queryText = $oldDef.QueryText.Replace("$sourceProjectName\", "$RightProject\")
$queryText = $queryText.Replace("[System.IterationPath] under '" + $sourceProjectName + "'", "[System.IterationPath] under '" + $RightProject +"'");
$newItem = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.QueryDefinition -ArgumentList @($subQuery.Name,$queryText)
if ($parentFolder.Contains($subQuery.Name) -ne $true )
{
$parentFolder.Add($newItem)
$WIStoreRight.Projects[$RightProject].QueryHierarchy.Save()
}
else
{
Write-Host "Query Definition $subQuery already exists" -foregroundcolor Yellow
}
}
}
}
[URI]$Projecturileft = "http://sourceServer:8080/tfs/SourceTPC/"
$leftProject = "SourceProject"
[URI]$ProjecturiRight = "http://targetServer:8080/tfs/TargetTPC/"
$RightProject = "TargetProject"
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.WorkItemTracking.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Server')
$tfsleft = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($Projecturileft)
$WIStoreleft = $tfsleft.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
$WIStoreleft.RefreshCache($true)
$WIStoreleft.SyncToCache() | out-null
$LeftQuerystore = $WIStoreleft.Projects[$LeftProject].QueryHierarchy
$tfsRight = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($ProjecturiRight)
$WIStoreRight = $tfsRight.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
$WIStoreRight.RefreshCache($true)
$WIStoreRight.SyncToCache() | out-null
foreach ($queryFolder in $LeftQuerystore)
{
if ($queryFolder.IsPersonal)
{
$RightMyQueriesFolder = [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder]$WIStoreRight.Projects[$RightProject].QueryHierarchy["My Queries"];
ProcessQueryItem $queryFolder $RightMyQueriesFolder $leftProject
Write-Host "Personal Queries Migrated"
}
}
Monday, July 15, 2013
How to use WIQL to query for work items in TFS 2012 via the URL
These days I spend a large amount of time in TFS 2012.
In TFS 2008 and 2010 users could use the q.aspx page to run a query using WIQL as defined by the 2010 WIQL spec (http://msdn.microsoft.com/en-us/library/bb130306(v=vs.100).aspx). Ken O' Bonn has a great post on this at http://blogs.msdn.com/b/kenobonn/archive/2009/05/28/how-to-link-directly-to-tswa-2008-or-2010-beta1-work-items-or-queries.aspx
However in TFS 2012 the URLs for everything changed and I was unable to find any information on how to accomplish the same task. How can you provide a URL to users that queries for work items without writing and saving a query to the TFS server.
The key is I don't want to save a query to TFS.
So after some trial and error I found the way!
http://<ServerAddress>:8080/tfs/<TPC Name>/<Project>/_workitems#_a=query&wiql=<WIQL>
An example:
http://tfstest:8080/TPC1/project1/_workItems#_a=query&wiql=select id, [System.areapath] from workitems
The WIQL for 2012 is defined at http://msdn.microsoft.com/en-us/library/bb130306.aspx#language
In TFS 2008 and 2010 users could use the q.aspx page to run a query using WIQL as defined by the 2010 WIQL spec (http://msdn.microsoft.com/en-us/library/bb130306(v=vs.100).aspx). Ken O' Bonn has a great post on this at http://blogs.msdn.com/b/kenobonn/archive/2009/05/28/how-to-link-directly-to-tswa-2008-or-2010-beta1-work-items-or-queries.aspx
However in TFS 2012 the URLs for everything changed and I was unable to find any information on how to accomplish the same task. How can you provide a URL to users that queries for work items without writing and saving a query to the TFS server.
The key is I don't want to save a query to TFS.
So after some trial and error I found the way!
http://<ServerAddress>:8080/tfs/<TPC Name>/<Project>/_workitems#_a=query&wiql=<WIQL>
An example:
http://tfstest:8080/TPC1/project1/_workItems#_a=query&wiql=select id, [System.areapath] from workitems
The WIQL for 2012 is defined at http://msdn.microsoft.com/en-us/library/bb130306.aspx#language
Subscribe to:
Posts (Atom)
10 Years from last post
Well world! After the last almost 10 years I have been up to a few things in life and work. Most recently I was working at Microsoft on ...
-
The TFS Build Activity logs have lots of useful information. Unfortunately this information is not real easy to get to. Using the TFS 2012...
-
Repost from old Blog Today I had a con-call to talk about disk quota and how to automate the setting and reading of Quota. So i thought t...


